3 ms·
I can count on ten fingers the number of times in twenty years I've worked with another bash coder who did the su and ssh cases correctly without triggering esc
by floatingatoll 7y ago
I can count on ten fingers the number of times in twenty years I've worked with another bash coder who did the su and ssh cases correctly without triggering escaping bugs. It's not any insult on them, but it's almost always done incorrectly and happens to work due to the absence of whitespace and backslashes, leading to eventual bugs (that Shellcheck won't always catch). Given:
# ARGV=( "one two", "three four" )
It's probably safe to recommend "$@" for use with su only whenyou use -c correctly, as you're locally specifying the args without any further IFS interference. But $* isn't usable:
# CORRECT
su root -c 'rm "$@"' -- "$@"
rm "one two" "three four"
# incorrect
su root -c "rm \"$@\""
rm one two three four # wrong arguments
# incorrect
su root -c "rm" "$@"
rm # -c doesn't use arguments
# incorrect
su root -c 'rm "$@"' "$@"
rm "three four" # loses the first argument (?!)
# incorrect:
su root "rm" "$*"
rm one two three four # wrong arguments
# incorrect:
su root "rm $*"
"rm one two three four" # command not found
It's probably safe to recommend "$@" for use with ssh only when using printf %q to ensure that you escape your arguments for their transit through ssh to the remote host, as otherwise the arguments get corrupted by the extra layer of shell processing. $* isn't usable here either:
# CORRECT
ssh remote -- 'rm '"$(printf '%q ' "$@")"
rm "one two" "three four"
# incorrect
ssh remote 'rm '$(printf '%q ' "$*")
rm "one two three four" # wrong arguments
# incorrect
ssh remote rm "$@"
rm one two three four # wrong arguments
# incorrect
ssh remote "rm \"$@\""
rm "one two three four" # wrong arguments
# incorrect
ssh remote 'rm "$@"' "$@"
rm one two three four # wrong arguments
# incorrect:
ssh remote "rm" "$*"
rm one two three four # wrong arguments
# incorrect:
ssh remote "rm" "$*"
rm one two three four # wrong arguments
EDIT: Shellcheck misses 3 of the 4 broken su cases, but catches all of the broken ssh cases. (And produced a warning I disagreed with in one of the complete examples, but in the spirit of things, added double quotes to silence it.)
- wadkar 7y agowow, this was very enlightening! I was wondering if you could elaborate a little bit on what you mean by: > as you're locally specifying the args without any further IFS interference I know that IFS = Input Field Separator. But how does it interfere when doing su root -c “command”? I might be missing something obvious here. Also the ssh + printf is gold! But to be honest, I would personally never use anything from $@ inside an ssh remote “command”. Too risky even with proper quoting. Thanks!
- floatingatoll 7y agoIFS controls the "$* joining character, if I remember correctly, and so if you use IFS=\0 then you can “safely” pass multiparty args in and then somehow take them apart on the other side. But this is the bad kind of crazy to me, and if I was reviewing code with this in it, I’d flag it for replacement. It’s the sort of thing that leads to feeling smart at working around knowing $@ but in the end it’s best just to truly understand $@ or use helper scripts to avoid quoting drama altogether. YMMV.
- hinkley 7y agoI’m confused why your correct rm examples include $@ twice. Do you mind explaining? I’ve been bitten by $@ and —- related mistakes at roughly the same time within the last month or so. Luckily nothing to do with sudo.
- floatingatoll 7y agoThis one, probably: # CORRECT su root -c 'rm "$@"' -- "$@" rm "one two" "three four" The command is going to be evaluated by bash, with ARGV set to what you pass after the hyphens as arguments to the bash environment that su spawns. Passing the arguments to su using "$@" to encode your calling function’s ARGV ensures that they’re uncorrupted into the su-spawned bash environment’s ARGV, and then executing the literal command rm "$@" ensures that they’re uncorrupted into the spawned rm command’s ARGV. You could fake this with printf %q if you tried hard enough but that’s a high-risk game to diagnose issues with, for example when you’re trying to figure out why newlines are reaching the command as the letter n.
- pwg 7y agosu root -c 'rm "$@"' -- "$@" The first instance ('rm "$@"') is part of the argument to -c. It is the "command" that su will have the spawned shell execute. The single quotes around the entire command pass the whole command, unchanged, onward to the shell that will be spawned, so that what is executed by that spawned shell is rm "$@" . The second instance is the argument list being given to su by the shell running the su, and it is just normal "$@" semantics there. One has to realize here that the second "$@" is being expanded by the current shell (the one running su) while the first "$@" is not expanded by the current shell, but is instead expanded by the spawned shell. The "$@" is needed twice, because two expansions ultimately take place, the first expansion occurs in the current shell, the second one is delayed and occurs in the spawned shell.
- hinkley 7y agoI had to read this several times before it sunk in, and in fact had to start formulating a follow up question before the bulb turned on. The first expansion is in the first process, second expansion in the child. So $@ number two is the input to $@ number one.
- dnautics 7y agoby ssh I meant this sort of thing: #!/bin/sh ssh remote "$*" invoked as follows (for example): ./ssh-remote.sh cat your-favorite-file.txt This works. I was mistaken about su / sudo.
- floatingatoll 7y agoIf the file name is “your file name.txt”, or if any argument contains a space character, your use will fail.