Escaping bash function arguments for use by su -c
Asked Answered
R

1

6

I'd like to be able to write a function su_mt_user that (currently) looks like this:

su_mt_user() {
    su someuser -c "$*"
}

The objective is to be able to use it like this:

su_mt_user mt-auth --stuff etc

Which would run the command mt-auth --stuff etc as user someuser. The current version works for this particular command, but it fails for commands like this:

some_filename_with_spaces="/home/user/hello there i like spaces in filenames.txt"
su_mt_user stat "$some_filename_with_spaces"

This fails with the following errors:

stat: cannot stat '/home/user/hello': No such file or directory
stat: cannot stat 'there': No such file or directory
stat: cannot stat 'i': No such file or directory
stat: cannot stat 'like': No such file or directory
stat: cannot stat 'spaces': No such file or directory
stat: cannot stat 'in': No such file or directory
stat: cannot stat 'filenames.txt': No such file or directory

I assume that this error happens because even though $some_filename_with_spaces is properly passed as one argument to the su_mt_user function, that function expands it to multiple arguments with "$*".

I've also tried this, trial-and-error:

su_mt_user() {
    su someuser -c "$0 $@"
}

But that fails as well (/usr/bin/stat: cannot execute binary file (what?))

Of course, stat "$some_filename_with_spaces" works as expected, from both the current user and the someuser user.

This looks like some escaping needs to be done, but does bash know how to do that? Does it need manual subtitution? If so, which characters need to be escaped?

Row answered 9/9, 2012 at 21:40 Comment(0)
T
13

To pass multiple arguments through a function to a command, you need "$@". "$@" is special in that even though it's between double quotes, the separate arguments end up in different words, so they're passed down exactly as is. This is different from $@ or $* without quotes, which would additionally split each argument where it contains whitespace and interpret each resulting word as a glob pattern, and from "$*", which coalesces all arguments into a single one with spaces in between.

There's an added wrinkle because su doesn't directly eat up arguments, they go through a shell. The non-option arguments to su are passed down as arguments to sh -c, and you then need an appropriate command for the -c.

su_mt_user() {
    su someuser -c '"$0" "$@"' -- "$@"
}
Tralee answered 9/9, 2012 at 22:56 Comment(2)
Thanks! Had to modify it a little bit to work by adding -- to prevent su from parsing option arguments as well: su someuser -c '"$0" "$@"' -- "$@"; otherwise, su attempts to interpret option arguments like -a as arguments to itself, causing an error.Row
I also had to modify this into su someuser -c '"$0" "$@"' -- "$0" "$@" because the command /bin/su -l root -c 'echo args "$@" this "$0"' -- a b c shows args b c this a.Lp

© 2022 - 2024 — McMap. All rights reserved.