Psyduck
Psyduck

Reputation: 667

sudo echo in bash only work with double quote, but not single quote

I have 2 variables PASSWORD="123" and FILEPATH="/dir-1/dir2", both are exported as env vars that visible to all users.

I am trying to echo to a file /dir-1/dir2/file that belongs to root as user1. I knew the issue that because shell redirect won't work with sudo, i.e. sudo echo <TEXT> >> <FILE>, so what I did are:

sudo bash -c 'echo "MY.Password $PASSWORD" >> ${FILEPATH}/file'

sudo bash -c 'echo MY.Password $PASSWORD >> ${FILEPATH}/file'

I was expecting to see the last line in the file to be:
MY.Password 123
But although the variable got substituted correctly, the expected content of MY.Password 123 does not appear as the last line in the /dir-1/dir2/file file.

I then tried

sudo bash -c "echo My.Password '$PASSWORD' >> ${FILEPATH}/file"  

sudo bash -c "echo My.Password $PASSWORD >> ${FILEPATH}/file"

Both worked.
Then I start to think if it's because of the single quote, I tried to escape the single quote with double quotes:

sudo bash -c 'echo "'"MY.Password $PASSWORD"'" >> "'"${FILEPATH}"'"/file'

This weird looking command also worked...

Can anyone explain me why my commands don't work, and why the last, weird looking, command worked?

Upvotes: 0

Views: 1987

Answers (1)

KamilCuk
KamilCuk

Reputation: 140900

sudo doesn't pass your environment by default. sudo has an option -E or --preserve-env that preserves existing environment inside the command run under sudo. Examples:

# let's set some variable `a`
a=Hello
# will print an empty line
sudo sh -c 'echo $a'
# will print Hello
sudo sh -c "echo $a"
# will print an empty line
sudo -E sh -c 'echo $a'
# because first we need to make `a` exported
export a
# this will print Hello
sudo -E sh -c 'echo $a'

When you use double quotes, the expansion happens before calling sudo. So expansion/execution goes like this:

$ sudo sh -c "echo $a"
+ sudo sh -c 'echo Hello'
+ sh -c 'echo Hello'
+ echo Hello
Hello

When you use single quotes the expansion/execution goes like this:

$ sudo sh -c 'echo $a'
+ sh -c 'echo $a'
+ echo $a
# now it depends if `$a` exists here, after `sudo` inside `sh`
+ echo

If the variable is not in environment, it will expand to nothing.

sudo bash -c 'echo "MY.Password $PASSWORD" >> ${FILEPATH}/file'

The PASSWORD and FILEPATH variables are not set inside sudo, so they expand to empty strings.

sudo bash -c "echo My.Password '$PASSWORD' >> ${FILEPATH}/file"  

First in your shell are the PASSWORD and FILEPATH variables expanded. Then sudo is executed, then bash is executed. Inside bash the echo My.Password 'PASSWORD' >> FILEPATH/file is executed so it works.

sudo bash -c 'echo "'"MY.Password $PASSWORD"'" >> "'"${FILEPATH}"'"/file'

Let's split it with newlines:

sudo bash -c 'echo "'\
"MY.Password $PASSWORD"\
'" >> "'\
"${FILEPATH}"\
'"/file'

Because your variables are inside " they are expanded before calling sudo. Then inside sudo they are already expanded and the values are properly quoted, the values are inside " in the child shell. This is the "most" correct form, because expanded values should be properly quoted, inside ", so that nothing strange happens when ex. FILEPATH has a space in it.

I usually do this:

 sudo bash -c 'echo MY.Password "$1" >> "$2"/file' -- "$PASSWORD" "$FILEPATH"

It makes me not worry about using " quotes inside the shell and nonetheless pass properly quoted variables to the shell without changing the environment.

Alternatively, you could do this:

 export PASSWORD FILEPATH
 sudo -E bash -c 'echo MY.Password "$PASSWORD" >> "$FILEPATH"/file'

Or go for a tea:

echo  MY.Password "$PASSWORD" | sudo tee -a "$FILEPATH"/file

Upvotes: 3

Related Questions