Error in command su

bashshellsutext processing

I am creating a command that will change a certain line in the /etc/profile file from a script, however in certain versions of Linux the sudo command may not be activated so it would be necessary to use the su command for this, within the script when executing:

Inside the /etc/profile file there is the following line:

if [ "`id -u`" = "0" ]; then
  echo $PATH | grep /usr/local/sbin 1> /dev/null 2> /dev/null
  if [ ! $? = 0 ]; then
    PATH=/usr/local/sbin:/usr/sbin:/sbin:$PATH
  fi
fi

So I did this:

$ PROFILE="/etc/profile"

$ sed '/\"`id -u`\"/ s/^if/# if/g;/\"`id -u`\"/ s/$/\nif [[ \"\$(id -u)\" == \"0\" || \"\$(id -un)\" == \"\${HOME##*\/}\" ]]; then/g;' ${PROFILE}

# if [ "`id -u`" = "0" ]; then
if [[ "$(id -u)" == "0" || "$(id -un)" == "${HOME##*/}" ]]; then
  echo $PATH | grep /usr/local/sbin 1> /dev/null 2> /dev/null
  if [ ! $? = 0 ]; then
    PATH=/usr/local/sbin:/usr/sbin:/sbin:$PATH
  fi
fi

The command appeared to execute correctly to change the file but when doing so using the su -c command:

$ PROFILE="/etc/profile"

$ su -c "sed '/\"`id -u`\"/ s/^if/# if/g;/\"`id -u`\"/ s/$/\nif [[ \"\$(id -u)\" == \"0\" || \"\$(id -un)\" == \"\${HOME##*\/}\" ]]; then/g;' ${PROFILE}"

if [ "`id -u`" = "0" ]; then
  echo $PATH | grep /usr/local/sbin 1> /dev/null 2> /dev/null
  if [ ! $? = 0 ]; then
    PATH=/usr/local/sbin:/usr/sbin:/sbin:$PATH
  fi
fi

It asks for the password and reads the file in the $PROFILE variable but does not modify the line. What is the problem with the su -c command?

Best Answer

N.B: All code was tested in a Slackware64 15.0 VM.

Literal answer

As @ilkkachu noted, in the second form of the command (using su(1)) you are using the single quotes inside of the double quotes. Single and double quotes follow different rules; also see [1] and [2]. Also, only one regex is necessary.

As @waltinator reminded, you should keep backups of any important modified files, which /etc/profile is. GNU sed has -i option to create backups, which regrettably isn't portable, so the alternative is to first create a backup copy of the modified file, and then operate on that backup copy and redirect stdout to the new file.

So, your command should be:

su -c 'cp /etc/profile /etc/profile.bak; sed "s,^\\(if \\[ \"\`id -u\`\".*\\)\$,\\#\\1\\
if [[ \"\$(id -u)\" == \"0\" || \"\$(id -un)\" == \"\${HOME##*/}\" ]];\
then," /etc/profile.bak > /etc/profile'

However, to stay compliant to POSIX sh(1p) (also: [3]), you should avoid non-POSIX constructs, such as

if [[ ... == ... || ... == ... ]]; then ...

Instead, you can use

if [ ... = ... ] || [ ... = ... ]; then ...

like this:

su -c 'cp /etc/profile /etc/profile.bak; sed "s,^\\(if \\[ \"\`id -u\`\".*\\)\$,\\#\\1\\
if [ \"\$(id -u)\" = \"0\" ] || [ \"\$(id -un)\" = \"\${HOME##*/}\" ];\
then," /etc/profile.bak > /etc/profile'

You can also avoid executing su altogether to change /etc/profile, if the file /etc/profile.bak already exists:

if [ ! -f /etc/profile.bak ]; then
su -c 'cp /etc/profile /etc/profile.bak; sed "s,^\\(if \\[ \"\`id -u\`\".*\\)\$,\\#\\1\\
if [ \"\$(id -u)\" = \"0\" ] || [ \"\$(id -un)\" = \"\${HOME##*/}\" ];\
then," /etc/profile.bak > /etc/profile'
fi

The alternative (correct) approach

From the comments, it seems that what you are actually trying to do is to automate the creation of user accounts which would be able to use sudo(8) and also have /usr/local/sbin:/usr/sbin:/sbin prepended to their PATH. This can easily be achieved without changing such a vital system file as /etc/profile.

In order to do that, you should:

  1. Login as root. If you create a script (say: createuser, which accepts username as first argument, let's call it newuser) to automate the creation of users, this can be substituted by passing it to sudo or su, eg. su -c 'createuser newuser'.
  2. (From here to end, possibly inside the script) Create new user account using the command:
useradd -m newuser
  1. Add newuser to /etc/sudoers, so it can execute sudo to obtain root privileges:
printf "newuser  ALL=(ALL:ALL) ALL\n" >> /etc/sudoers

Inside of a script, this will be a bit different. If you have saved the username in a variable user:

printf "%s  ALL=(ALL:ALL) ALL\n" "$user" >> /etc/sudoers
  1. Prepend /usr/local/sbin:/usr/sbin:/sbin to PATH in /home/newuser/.profile to customize his environment:
printf "%s\n" 'export PATH=/usr/local/sbin:/usr/sbin:/sbin${PATH+:}"$PATH"' \
      >>/home/newuser/.profile
  1. Change ownership and permissions on /home/newuser/.profile:
chown newuser:newuser /home/newuser/.profile
chmod 644 /home/newuser/.profile
  1. Set password for newuser (needs tty to change password):
passwd newuser

or set it to "expired", which will force the user to set it on next login, and doesn't require a tty:

passwd -e newuser

Addendum

  • If you meant to combine the two requirements, and set or preserve PATH inside of an environment created by executing sudo, see How to make sudo preserve $PATH?. Note that no additional steps other than the ones from the "alternative approach" above are necessary by default in Slackware64 15.0:
newuser@darkstar:~$ sudo sh -c 'printf "%s\n" "$PATH"'
/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin
  • From the comment to this answer, it seems that the assumption from this answer was incorrect, and you are trying to change sudo privileges for the existing user. This is not much different from the procedure outlined here, though. The basic idea is the same: you need to login as root and login as the newly-privileged user after giving him the privileges. Alternatively, this works (tested):
newuser@darkstar:~$ su -c 'printf "newuser  ALL=(ALL:ALL) ALL\n" >>/etc/sudoers'
newuser@darkstar:~$ sudo ls -la /root
Password:
total 24
drwx--x---  3 root root 4096 May 5 21:13 .
[...]

or, if you want to not "hard-code" the username, but use the current username:

newuser@darkstar:~$ su -c "printf \"%s  ALL=(ALL:ALL) ALL\\n\" \"$(id -un)\" >>/etc/sudoers"
newuser@darkstar:~$ sudo ls -la /root
Password:
total 24
drwx--x---  3 root root 4096 May 5 21:13 .
[...]

Again, mind the quotes, single-quotes and backslash-escapes and how they relate to each other. See the references for more information.

  • Changing the PATH in the existing session by modifying $HOME/.profile to include setting it to a new value, logically, requires either re-sourcing $HOME/.profile after modification:
. ~/.profile

or relogging.

References

  1. POSIX Shell Command Language, (2.2 Quoting, 2.2.2 Single-Quotes, 2.2.3 Double-Quotes),
    https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02
  2. Bash manual, 3.1.2 Quoting,
    https://www.gnu.org/software/bash/manual/html_node/Quoting.html
  3. POSIX Shell Command Language,
    https://pubs.opengroup.org/onlinepubs/9699919799/idx/shell.html
Related Question