“sh -c” does not expand positional parameters, if I run it from “sudo --login”. Is there a way around this?Change directory and then chain (`exec`) a command?How to use SCSUDO to run scripts with positional parameters?How to run this in sudo?Set sudo password differently from login oneIs there a way to run sudo without running another unneccessary command?How to read from a pipe keeping positional parameters?Why can I run `sudo bash` but not `sudo su`?Is there a way to get the positional parameters of the script from inside a function in bash?Why does sudo accept password not from stdin by default?

Journal published a paper, ignoring my objections as a referee

Why is there not a willingness from the world to step in between Pakistan and India?

What checks exist against overuse of presidential pardons in the USA?

Wrong Stamping of UK Visa

Are sweatpants frowned upon on flights?

Why didn't Doc believe Marty was from the future?

Looking for a plural noun related to ‘fulcrum’ or ‘pivot’ that denotes multiple things as crucial to success

How do you say "half the time …, the other half …" in German?

What is Soda Fountain Etiquette?

Employing a contractor proving difficult

Do multi-engine jets need all engines with equal age to reduce asymmetry in thrust and fuel consumption arising out of deterioration?

Get contents before a colon

Another "Ask One Question" Question

How do I portray irrational anger in first person?

In Endgame, wouldn't Stark have remembered Hulk busting out of the stairwell?

Is the Amazon rainforest the "world's lungs"?

Is there a word or phrase that means "use other people's wifi or Internet service without consent"?

Why can't you say don't instead of won't?

How did medieval manors handle population growth? Was there room for more fields to be ploughed?

Term used to describe a person who predicts future outcomes

Why does Sauron not permit his followers to use his name?

Coupling two 15 Amp circuit breaker for 20 Amp

Why does AM radio react to IR remote?

I feel cheated on by my new employer, does this sound right?



“sh -c” does not expand positional parameters, if I run it from “sudo --login”. Is there a way around this?


Change directory and then chain (`exec`) a command?How to use SCSUDO to run scripts with positional parameters?How to run this in sudo?Set sudo password differently from login oneIs there a way to run sudo without running another unneccessary command?How to read from a pipe keeping positional parameters?Why can I run `sudo bash` but not `sudo su`?Is there a way to get the positional parameters of the script from inside a function in bash?Why does sudo accept password not from stdin by default?






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








4















I wrote an "inline script" which uses positional parameters, e.g.



$ sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


I actually want to run my inline script using sudo --login. But then the positional parameters don't work. Is there a way to make them work?



(I'm also interested if the behaviour is documented or standardized somewhere).



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=-bash
p1=
p2=
all=1 2


Software versions:



$ sh --version
GNU bash, version 5.0.7(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ sudo --version
Sudo version 1.8.27
Sudoers policy plugin version 1.8.27
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.27









share|improve this question



















  • 1





    sudo -i is essentially running it with the user's shell. It's as if the whole thing was wrapped in bash -c. Wrap your own command in bash -c and maybe some mess of quoting might work.

    – muru
    Aug 16 at 18:58











  • @muru I see, yes. Thanks. I was rather hoping to avoid quoting messes :-).

    – sourcejedi
    Aug 16 at 19:01











  • @muru I used strace -f -e trace=execve, and found you were right that sudo is wrapping everything into a single string, to call bash --login -c '...'. Although, that doesn't tell us exactly why the values can be seen in "$@", but not $1 or $2. (And then I checked further; they are also not in $3 nor $4 nor $5 nor $6).

    – sourcejedi
    Aug 16 at 19:16












  • if you want to avoid a mess of quoting (and escaping), just write a shell script and run it with sudo. It'll be a lot easier to write and to debug than trying to do it on the command-line, even if it's just a one-use throwaway script.

    – cas
    Aug 17 at 2:30

















4















I wrote an "inline script" which uses positional parameters, e.g.



$ sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


I actually want to run my inline script using sudo --login. But then the positional parameters don't work. Is there a way to make them work?



(I'm also interested if the behaviour is documented or standardized somewhere).



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=-bash
p1=
p2=
all=1 2


Software versions:



$ sh --version
GNU bash, version 5.0.7(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ sudo --version
Sudo version 1.8.27
Sudoers policy plugin version 1.8.27
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.27









share|improve this question



















  • 1





    sudo -i is essentially running it with the user's shell. It's as if the whole thing was wrapped in bash -c. Wrap your own command in bash -c and maybe some mess of quoting might work.

    – muru
    Aug 16 at 18:58











  • @muru I see, yes. Thanks. I was rather hoping to avoid quoting messes :-).

    – sourcejedi
    Aug 16 at 19:01











  • @muru I used strace -f -e trace=execve, and found you were right that sudo is wrapping everything into a single string, to call bash --login -c '...'. Although, that doesn't tell us exactly why the values can be seen in "$@", but not $1 or $2. (And then I checked further; they are also not in $3 nor $4 nor $5 nor $6).

    – sourcejedi
    Aug 16 at 19:16












  • if you want to avoid a mess of quoting (and escaping), just write a shell script and run it with sudo. It'll be a lot easier to write and to debug than trying to do it on the command-line, even if it's just a one-use throwaway script.

    – cas
    Aug 17 at 2:30













4












4








4


2






I wrote an "inline script" which uses positional parameters, e.g.



$ sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


I actually want to run my inline script using sudo --login. But then the positional parameters don't work. Is there a way to make them work?



(I'm also interested if the behaviour is documented or standardized somewhere).



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=-bash
p1=
p2=
all=1 2


Software versions:



$ sh --version
GNU bash, version 5.0.7(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ sudo --version
Sudo version 1.8.27
Sudoers policy plugin version 1.8.27
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.27









share|improve this question














I wrote an "inline script" which uses positional parameters, e.g.



$ sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


I actually want to run my inline script using sudo --login. But then the positional parameters don't work. Is there a way to make them work?



(I'm also interested if the behaviour is documented or standardized somewhere).



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=-bash
p1=
p2=
all=1 2


Software versions:



$ sh --version
GNU bash, version 5.0.7(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ sudo --version
Sudo version 1.8.27
Sudoers policy plugin version 1.8.27
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.27






shell-script sudo






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Aug 16 at 18:37









sourcejedisourcejedi

29.4k5 gold badges52 silver badges131 bronze badges




29.4k5 gold badges52 silver badges131 bronze badges










  • 1





    sudo -i is essentially running it with the user's shell. It's as if the whole thing was wrapped in bash -c. Wrap your own command in bash -c and maybe some mess of quoting might work.

    – muru
    Aug 16 at 18:58











  • @muru I see, yes. Thanks. I was rather hoping to avoid quoting messes :-).

    – sourcejedi
    Aug 16 at 19:01











  • @muru I used strace -f -e trace=execve, and found you were right that sudo is wrapping everything into a single string, to call bash --login -c '...'. Although, that doesn't tell us exactly why the values can be seen in "$@", but not $1 or $2. (And then I checked further; they are also not in $3 nor $4 nor $5 nor $6).

    – sourcejedi
    Aug 16 at 19:16












  • if you want to avoid a mess of quoting (and escaping), just write a shell script and run it with sudo. It'll be a lot easier to write and to debug than trying to do it on the command-line, even if it's just a one-use throwaway script.

    – cas
    Aug 17 at 2:30












  • 1





    sudo -i is essentially running it with the user's shell. It's as if the whole thing was wrapped in bash -c. Wrap your own command in bash -c and maybe some mess of quoting might work.

    – muru
    Aug 16 at 18:58











  • @muru I see, yes. Thanks. I was rather hoping to avoid quoting messes :-).

    – sourcejedi
    Aug 16 at 19:01











  • @muru I used strace -f -e trace=execve, and found you were right that sudo is wrapping everything into a single string, to call bash --login -c '...'. Although, that doesn't tell us exactly why the values can be seen in "$@", but not $1 or $2. (And then I checked further; they are also not in $3 nor $4 nor $5 nor $6).

    – sourcejedi
    Aug 16 at 19:16












  • if you want to avoid a mess of quoting (and escaping), just write a shell script and run it with sudo. It'll be a lot easier to write and to debug than trying to do it on the command-line, even if it's just a one-use throwaway script.

    – cas
    Aug 17 at 2:30







1




1





sudo -i is essentially running it with the user's shell. It's as if the whole thing was wrapped in bash -c. Wrap your own command in bash -c and maybe some mess of quoting might work.

– muru
Aug 16 at 18:58





sudo -i is essentially running it with the user's shell. It's as if the whole thing was wrapped in bash -c. Wrap your own command in bash -c and maybe some mess of quoting might work.

– muru
Aug 16 at 18:58













@muru I see, yes. Thanks. I was rather hoping to avoid quoting messes :-).

– sourcejedi
Aug 16 at 19:01





@muru I see, yes. Thanks. I was rather hoping to avoid quoting messes :-).

– sourcejedi
Aug 16 at 19:01













@muru I used strace -f -e trace=execve, and found you were right that sudo is wrapping everything into a single string, to call bash --login -c '...'. Although, that doesn't tell us exactly why the values can be seen in "$@", but not $1 or $2. (And then I checked further; they are also not in $3 nor $4 nor $5 nor $6).

– sourcejedi
Aug 16 at 19:16






@muru I used strace -f -e trace=execve, and found you were right that sudo is wrapping everything into a single string, to call bash --login -c '...'. Although, that doesn't tell us exactly why the values can be seen in "$@", but not $1 or $2. (And then I checked further; they are also not in $3 nor $4 nor $5 nor $6).

– sourcejedi
Aug 16 at 19:16














if you want to avoid a mess of quoting (and escaping), just write a shell script and run it with sudo. It'll be a lot easier to write and to debug than trying to do it on the command-line, even if it's just a one-use throwaway script.

– cas
Aug 17 at 2:30





if you want to avoid a mess of quoting (and escaping), just write a shell script and run it with sudo. It'll be a lot easier to write and to debug than trying to do it on the command-line, even if it's just a one-use throwaway script.

– cas
Aug 17 at 2:30










1 Answer
1






active

oldest

votes


















6















For the same reason



sudo --login echo '$HOME'


Doesn't output $HOME, but the content of the $HOME variable (of the target user, as set by sudo).



In your case, it's not because sh -c doesn't expand the positional parameters, but because the $0, $1, $2 were already expanded (by the login shell) by the time sh was started.



What is more surprising here is why you see all=1 2. And it's the same reason why you see $@ upon sudo --login echo '$@'.



Both -s/--shell and -i/--login run a shell to run the command. But sudo does it in a very strange way.



I'd expect sudo -s 'some shell code' to run a shell to interpret some shell code, but that's not what it does. With -s, it still kind-of expects to run a single simple command, and tries to quote (with ) some characters in the hope of making it happen through that shell.



You're not meant to do sudo -s 'echo test', but sudo -s echo test. In the first case, sudo actually passes echo test as shell code. In Bourne-like shells, that tries to run a command called 'echo test'. With a rc shell, that would run a command called echo with test as argument.



Beside SPC, there are a few characters that sudo escapes. That includes backtick, ;, |, (, ), *, &, =, but strangely enough, not $ (or maybe that's the only raison d'être of those -s/--login options: to have a shell expand variables).



It does include @ though. If you look at the code, it escapes all bytes except the ASCII alnums, _, - and $.




 if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\';



So:



sudo -s echo '$@'


sudo actually runs "$SHELL" -c 'echo $@' and that explains why in your example, $@ is not expanded by the shell started by sudo --login but by the one you tell it to run.



In your



sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2


sudo runs root's login shell (bash in your case) and tells it to interpret:



sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


The arguments have been joined with spaces and all the SPC, ", =, &, @ characters, but not $ have been escaped. Because those $ have not been escaped that bash login shell will expand $0,$1, $2 but not $@ because of that backslash.



You can see it with:



$ SHELL=echo sudo -s sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
-c sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


So that login bash shell ends up running sh with -c as first argument,



echo "p0=-bash" && echo "p1=" && echo "p2=" && echo "all=$@"


as second argument and sh, 1, 2 as 3rd, 4th and 5th.



To work around it, you can use $0 instead of $0, because sudo transforms it to $0 which prevents the login shell from expanding it to the content of its $0:



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


Edit: history brings light to the reason behind it



more findings while looking at the code. The non-escaping of $ was apparently introduced in 2013 by this change. Which refers to bug#564 which refers to bug#413.



It looks like before that bug#413 was "fixed", sudo behaved as I expected.



That is:



sudo -i 'some shell code'


Had the login shell interpret that shell code (and sudo -s 'some shell code' had $SHELL interpret that shell code). But the bug#413 resolution broke it, because the person who reported the bug didn't understand it worked that way. And the bug#564 broke it further (in an attempt to revert only part of the breakage introduced by the bug#413 resolution).



I went as far as compiling sudo 1.7.1 from 2009 and the -s/-i options were working as I'd expect then. 1.7.3 (with bug#413 resolution) worked kind of like you apparently expected:



$ sudo-1.7.1 -i 'echo "$SHELL"'
/bin/bash
$ sudo-1.7.3 -i 'echo "$SHELL"'
-bash: echo "$SHELL": command not found
$ sudo-1.8.21p2 -i 'echo "$SHELL"'
-bash: echo "/bin/bash": No such file or directory


$ sudo-1.7.3 -i sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


That escaping introduced in the fix for that bug#413 is easily fooled because banging a in front of all bytes (not character) doesn't work for all.



Beside the obvious case of rc where is not even a quoting operator, in most shells, newline can't be quoted with :



$ SHELL=sh sudo -s echo $'anb'
ab
$ SHELL=csh sudo -s echo $'anb'
a b


It also means empty arguments are discarded:



$ sudo printf '<%s>n' a '' b
<a>
<>
<b>
$ sudo -s printf '<%s>n' a '' b
<a>
<b>


Also, by inserting a before every byte, it's transforming characters into others in charsets where byte 0x5c (the encoding of ) is found in other characters:



$ SHELL=bash LC_ALL=zh_HK.big5hkscs sudo -s echo $'xa3``uname`xa3`'
bash: line 2: Linuxα: command not found
α


0xa3 0x60 is Greek Epsilon in that locale. And sudo changes the 0x60s to 0x5c 0x60 and 0xa3 0x5c is Greek Alpha, which explains why the uname command was run. sudo changed the



ε`uname`ε


to



α``uname`α`


And of course the behaviour ends up being surprising in that the $-, $$, positional parameters and $varname (where varname is a valid POSIX variable name) are expanded but not the other parameters (like $@ here, but also $!, $?, $*, $#) or $varname or $var[1] (csh/tcsh/zsh) or $var(1) (rc/es/...).






share|improve this answer



























  • Ow, that's horrible. What do they propose to "fix" with quoting the arguments?

    – Kusalananda
    Aug 16 at 20:29











  • Answer accepted, in the spirit of "sudo does it in a very strange way". I do not guarantee that sudo on my system does everything written (or implied?) in this answer, as per my comment "I was rather hoping to avoid quoting messes :-)".

    – sourcejedi
    Aug 16 at 20:30






  • 1





    @Kusalananda, I never understood why sudo handled -s/--login that way. To me, it makes it pointless.

    – Stéphane Chazelas
    Aug 16 at 20:31











  • @Kusalananda, see edit with the historical context that explains the current mess.

    – Stéphane Chazelas
    Aug 16 at 21:23













Your Answer








StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "106"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f535925%2fsh-c-does-not-expand-positional-parameters-if-i-run-it-from-sudo-login%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









6















For the same reason



sudo --login echo '$HOME'


Doesn't output $HOME, but the content of the $HOME variable (of the target user, as set by sudo).



In your case, it's not because sh -c doesn't expand the positional parameters, but because the $0, $1, $2 were already expanded (by the login shell) by the time sh was started.



What is more surprising here is why you see all=1 2. And it's the same reason why you see $@ upon sudo --login echo '$@'.



Both -s/--shell and -i/--login run a shell to run the command. But sudo does it in a very strange way.



I'd expect sudo -s 'some shell code' to run a shell to interpret some shell code, but that's not what it does. With -s, it still kind-of expects to run a single simple command, and tries to quote (with ) some characters in the hope of making it happen through that shell.



You're not meant to do sudo -s 'echo test', but sudo -s echo test. In the first case, sudo actually passes echo test as shell code. In Bourne-like shells, that tries to run a command called 'echo test'. With a rc shell, that would run a command called echo with test as argument.



Beside SPC, there are a few characters that sudo escapes. That includes backtick, ;, |, (, ), *, &, =, but strangely enough, not $ (or maybe that's the only raison d'être of those -s/--login options: to have a shell expand variables).



It does include @ though. If you look at the code, it escapes all bytes except the ASCII alnums, _, - and $.




 if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\';



So:



sudo -s echo '$@'


sudo actually runs "$SHELL" -c 'echo $@' and that explains why in your example, $@ is not expanded by the shell started by sudo --login but by the one you tell it to run.



In your



sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2


sudo runs root's login shell (bash in your case) and tells it to interpret:



sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


The arguments have been joined with spaces and all the SPC, ", =, &, @ characters, but not $ have been escaped. Because those $ have not been escaped that bash login shell will expand $0,$1, $2 but not $@ because of that backslash.



You can see it with:



$ SHELL=echo sudo -s sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
-c sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


So that login bash shell ends up running sh with -c as first argument,



echo "p0=-bash" && echo "p1=" && echo "p2=" && echo "all=$@"


as second argument and sh, 1, 2 as 3rd, 4th and 5th.



To work around it, you can use $0 instead of $0, because sudo transforms it to $0 which prevents the login shell from expanding it to the content of its $0:



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


Edit: history brings light to the reason behind it



more findings while looking at the code. The non-escaping of $ was apparently introduced in 2013 by this change. Which refers to bug#564 which refers to bug#413.



It looks like before that bug#413 was "fixed", sudo behaved as I expected.



That is:



sudo -i 'some shell code'


Had the login shell interpret that shell code (and sudo -s 'some shell code' had $SHELL interpret that shell code). But the bug#413 resolution broke it, because the person who reported the bug didn't understand it worked that way. And the bug#564 broke it further (in an attempt to revert only part of the breakage introduced by the bug#413 resolution).



I went as far as compiling sudo 1.7.1 from 2009 and the -s/-i options were working as I'd expect then. 1.7.3 (with bug#413 resolution) worked kind of like you apparently expected:



$ sudo-1.7.1 -i 'echo "$SHELL"'
/bin/bash
$ sudo-1.7.3 -i 'echo "$SHELL"'
-bash: echo "$SHELL": command not found
$ sudo-1.8.21p2 -i 'echo "$SHELL"'
-bash: echo "/bin/bash": No such file or directory


$ sudo-1.7.3 -i sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


That escaping introduced in the fix for that bug#413 is easily fooled because banging a in front of all bytes (not character) doesn't work for all.



Beside the obvious case of rc where is not even a quoting operator, in most shells, newline can't be quoted with :



$ SHELL=sh sudo -s echo $'anb'
ab
$ SHELL=csh sudo -s echo $'anb'
a b


It also means empty arguments are discarded:



$ sudo printf '<%s>n' a '' b
<a>
<>
<b>
$ sudo -s printf '<%s>n' a '' b
<a>
<b>


Also, by inserting a before every byte, it's transforming characters into others in charsets where byte 0x5c (the encoding of ) is found in other characters:



$ SHELL=bash LC_ALL=zh_HK.big5hkscs sudo -s echo $'xa3``uname`xa3`'
bash: line 2: Linuxα: command not found
α


0xa3 0x60 is Greek Epsilon in that locale. And sudo changes the 0x60s to 0x5c 0x60 and 0xa3 0x5c is Greek Alpha, which explains why the uname command was run. sudo changed the



ε`uname`ε


to



α``uname`α`


And of course the behaviour ends up being surprising in that the $-, $$, positional parameters and $varname (where varname is a valid POSIX variable name) are expanded but not the other parameters (like $@ here, but also $!, $?, $*, $#) or $varname or $var[1] (csh/tcsh/zsh) or $var(1) (rc/es/...).






share|improve this answer



























  • Ow, that's horrible. What do they propose to "fix" with quoting the arguments?

    – Kusalananda
    Aug 16 at 20:29











  • Answer accepted, in the spirit of "sudo does it in a very strange way". I do not guarantee that sudo on my system does everything written (or implied?) in this answer, as per my comment "I was rather hoping to avoid quoting messes :-)".

    – sourcejedi
    Aug 16 at 20:30






  • 1





    @Kusalananda, I never understood why sudo handled -s/--login that way. To me, it makes it pointless.

    – Stéphane Chazelas
    Aug 16 at 20:31











  • @Kusalananda, see edit with the historical context that explains the current mess.

    – Stéphane Chazelas
    Aug 16 at 21:23















6















For the same reason



sudo --login echo '$HOME'


Doesn't output $HOME, but the content of the $HOME variable (of the target user, as set by sudo).



In your case, it's not because sh -c doesn't expand the positional parameters, but because the $0, $1, $2 were already expanded (by the login shell) by the time sh was started.



What is more surprising here is why you see all=1 2. And it's the same reason why you see $@ upon sudo --login echo '$@'.



Both -s/--shell and -i/--login run a shell to run the command. But sudo does it in a very strange way.



I'd expect sudo -s 'some shell code' to run a shell to interpret some shell code, but that's not what it does. With -s, it still kind-of expects to run a single simple command, and tries to quote (with ) some characters in the hope of making it happen through that shell.



You're not meant to do sudo -s 'echo test', but sudo -s echo test. In the first case, sudo actually passes echo test as shell code. In Bourne-like shells, that tries to run a command called 'echo test'. With a rc shell, that would run a command called echo with test as argument.



Beside SPC, there are a few characters that sudo escapes. That includes backtick, ;, |, (, ), *, &, =, but strangely enough, not $ (or maybe that's the only raison d'être of those -s/--login options: to have a shell expand variables).



It does include @ though. If you look at the code, it escapes all bytes except the ASCII alnums, _, - and $.




 if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\';



So:



sudo -s echo '$@'


sudo actually runs "$SHELL" -c 'echo $@' and that explains why in your example, $@ is not expanded by the shell started by sudo --login but by the one you tell it to run.



In your



sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2


sudo runs root's login shell (bash in your case) and tells it to interpret:



sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


The arguments have been joined with spaces and all the SPC, ", =, &, @ characters, but not $ have been escaped. Because those $ have not been escaped that bash login shell will expand $0,$1, $2 but not $@ because of that backslash.



You can see it with:



$ SHELL=echo sudo -s sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
-c sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


So that login bash shell ends up running sh with -c as first argument,



echo "p0=-bash" && echo "p1=" && echo "p2=" && echo "all=$@"


as second argument and sh, 1, 2 as 3rd, 4th and 5th.



To work around it, you can use $0 instead of $0, because sudo transforms it to $0 which prevents the login shell from expanding it to the content of its $0:



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


Edit: history brings light to the reason behind it



more findings while looking at the code. The non-escaping of $ was apparently introduced in 2013 by this change. Which refers to bug#564 which refers to bug#413.



It looks like before that bug#413 was "fixed", sudo behaved as I expected.



That is:



sudo -i 'some shell code'


Had the login shell interpret that shell code (and sudo -s 'some shell code' had $SHELL interpret that shell code). But the bug#413 resolution broke it, because the person who reported the bug didn't understand it worked that way. And the bug#564 broke it further (in an attempt to revert only part of the breakage introduced by the bug#413 resolution).



I went as far as compiling sudo 1.7.1 from 2009 and the -s/-i options were working as I'd expect then. 1.7.3 (with bug#413 resolution) worked kind of like you apparently expected:



$ sudo-1.7.1 -i 'echo "$SHELL"'
/bin/bash
$ sudo-1.7.3 -i 'echo "$SHELL"'
-bash: echo "$SHELL": command not found
$ sudo-1.8.21p2 -i 'echo "$SHELL"'
-bash: echo "/bin/bash": No such file or directory


$ sudo-1.7.3 -i sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


That escaping introduced in the fix for that bug#413 is easily fooled because banging a in front of all bytes (not character) doesn't work for all.



Beside the obvious case of rc where is not even a quoting operator, in most shells, newline can't be quoted with :



$ SHELL=sh sudo -s echo $'anb'
ab
$ SHELL=csh sudo -s echo $'anb'
a b


It also means empty arguments are discarded:



$ sudo printf '<%s>n' a '' b
<a>
<>
<b>
$ sudo -s printf '<%s>n' a '' b
<a>
<b>


Also, by inserting a before every byte, it's transforming characters into others in charsets where byte 0x5c (the encoding of ) is found in other characters:



$ SHELL=bash LC_ALL=zh_HK.big5hkscs sudo -s echo $'xa3``uname`xa3`'
bash: line 2: Linuxα: command not found
α


0xa3 0x60 is Greek Epsilon in that locale. And sudo changes the 0x60s to 0x5c 0x60 and 0xa3 0x5c is Greek Alpha, which explains why the uname command was run. sudo changed the



ε`uname`ε


to



α``uname`α`


And of course the behaviour ends up being surprising in that the $-, $$, positional parameters and $varname (where varname is a valid POSIX variable name) are expanded but not the other parameters (like $@ here, but also $!, $?, $*, $#) or $varname or $var[1] (csh/tcsh/zsh) or $var(1) (rc/es/...).






share|improve this answer



























  • Ow, that's horrible. What do they propose to "fix" with quoting the arguments?

    – Kusalananda
    Aug 16 at 20:29











  • Answer accepted, in the spirit of "sudo does it in a very strange way". I do not guarantee that sudo on my system does everything written (or implied?) in this answer, as per my comment "I was rather hoping to avoid quoting messes :-)".

    – sourcejedi
    Aug 16 at 20:30






  • 1





    @Kusalananda, I never understood why sudo handled -s/--login that way. To me, it makes it pointless.

    – Stéphane Chazelas
    Aug 16 at 20:31











  • @Kusalananda, see edit with the historical context that explains the current mess.

    – Stéphane Chazelas
    Aug 16 at 21:23













6














6










6









For the same reason



sudo --login echo '$HOME'


Doesn't output $HOME, but the content of the $HOME variable (of the target user, as set by sudo).



In your case, it's not because sh -c doesn't expand the positional parameters, but because the $0, $1, $2 were already expanded (by the login shell) by the time sh was started.



What is more surprising here is why you see all=1 2. And it's the same reason why you see $@ upon sudo --login echo '$@'.



Both -s/--shell and -i/--login run a shell to run the command. But sudo does it in a very strange way.



I'd expect sudo -s 'some shell code' to run a shell to interpret some shell code, but that's not what it does. With -s, it still kind-of expects to run a single simple command, and tries to quote (with ) some characters in the hope of making it happen through that shell.



You're not meant to do sudo -s 'echo test', but sudo -s echo test. In the first case, sudo actually passes echo test as shell code. In Bourne-like shells, that tries to run a command called 'echo test'. With a rc shell, that would run a command called echo with test as argument.



Beside SPC, there are a few characters that sudo escapes. That includes backtick, ;, |, (, ), *, &, =, but strangely enough, not $ (or maybe that's the only raison d'être of those -s/--login options: to have a shell expand variables).



It does include @ though. If you look at the code, it escapes all bytes except the ASCII alnums, _, - and $.




 if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\';



So:



sudo -s echo '$@'


sudo actually runs "$SHELL" -c 'echo $@' and that explains why in your example, $@ is not expanded by the shell started by sudo --login but by the one you tell it to run.



In your



sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2


sudo runs root's login shell (bash in your case) and tells it to interpret:



sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


The arguments have been joined with spaces and all the SPC, ", =, &, @ characters, but not $ have been escaped. Because those $ have not been escaped that bash login shell will expand $0,$1, $2 but not $@ because of that backslash.



You can see it with:



$ SHELL=echo sudo -s sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
-c sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


So that login bash shell ends up running sh with -c as first argument,



echo "p0=-bash" && echo "p1=" && echo "p2=" && echo "all=$@"


as second argument and sh, 1, 2 as 3rd, 4th and 5th.



To work around it, you can use $0 instead of $0, because sudo transforms it to $0 which prevents the login shell from expanding it to the content of its $0:



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


Edit: history brings light to the reason behind it



more findings while looking at the code. The non-escaping of $ was apparently introduced in 2013 by this change. Which refers to bug#564 which refers to bug#413.



It looks like before that bug#413 was "fixed", sudo behaved as I expected.



That is:



sudo -i 'some shell code'


Had the login shell interpret that shell code (and sudo -s 'some shell code' had $SHELL interpret that shell code). But the bug#413 resolution broke it, because the person who reported the bug didn't understand it worked that way. And the bug#564 broke it further (in an attempt to revert only part of the breakage introduced by the bug#413 resolution).



I went as far as compiling sudo 1.7.1 from 2009 and the -s/-i options were working as I'd expect then. 1.7.3 (with bug#413 resolution) worked kind of like you apparently expected:



$ sudo-1.7.1 -i 'echo "$SHELL"'
/bin/bash
$ sudo-1.7.3 -i 'echo "$SHELL"'
-bash: echo "$SHELL": command not found
$ sudo-1.8.21p2 -i 'echo "$SHELL"'
-bash: echo "/bin/bash": No such file or directory


$ sudo-1.7.3 -i sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


That escaping introduced in the fix for that bug#413 is easily fooled because banging a in front of all bytes (not character) doesn't work for all.



Beside the obvious case of rc where is not even a quoting operator, in most shells, newline can't be quoted with :



$ SHELL=sh sudo -s echo $'anb'
ab
$ SHELL=csh sudo -s echo $'anb'
a b


It also means empty arguments are discarded:



$ sudo printf '<%s>n' a '' b
<a>
<>
<b>
$ sudo -s printf '<%s>n' a '' b
<a>
<b>


Also, by inserting a before every byte, it's transforming characters into others in charsets where byte 0x5c (the encoding of ) is found in other characters:



$ SHELL=bash LC_ALL=zh_HK.big5hkscs sudo -s echo $'xa3``uname`xa3`'
bash: line 2: Linuxα: command not found
α


0xa3 0x60 is Greek Epsilon in that locale. And sudo changes the 0x60s to 0x5c 0x60 and 0xa3 0x5c is Greek Alpha, which explains why the uname command was run. sudo changed the



ε`uname`ε


to



α``uname`α`


And of course the behaviour ends up being surprising in that the $-, $$, positional parameters and $varname (where varname is a valid POSIX variable name) are expanded but not the other parameters (like $@ here, but also $!, $?, $*, $#) or $varname or $var[1] (csh/tcsh/zsh) or $var(1) (rc/es/...).






share|improve this answer















For the same reason



sudo --login echo '$HOME'


Doesn't output $HOME, but the content of the $HOME variable (of the target user, as set by sudo).



In your case, it's not because sh -c doesn't expand the positional parameters, but because the $0, $1, $2 were already expanded (by the login shell) by the time sh was started.



What is more surprising here is why you see all=1 2. And it's the same reason why you see $@ upon sudo --login echo '$@'.



Both -s/--shell and -i/--login run a shell to run the command. But sudo does it in a very strange way.



I'd expect sudo -s 'some shell code' to run a shell to interpret some shell code, but that's not what it does. With -s, it still kind-of expects to run a single simple command, and tries to quote (with ) some characters in the hope of making it happen through that shell.



You're not meant to do sudo -s 'echo test', but sudo -s echo test. In the first case, sudo actually passes echo test as shell code. In Bourne-like shells, that tries to run a command called 'echo test'. With a rc shell, that would run a command called echo with test as argument.



Beside SPC, there are a few characters that sudo escapes. That includes backtick, ;, |, (, ), *, &, =, but strangely enough, not $ (or maybe that's the only raison d'être of those -s/--login options: to have a shell expand variables).



It does include @ though. If you look at the code, it escapes all bytes except the ASCII alnums, _, - and $.




 if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\';



So:



sudo -s echo '$@'


sudo actually runs "$SHELL" -c 'echo $@' and that explains why in your example, $@ is not expanded by the shell started by sudo --login but by the one you tell it to run.



In your



sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2


sudo runs root's login shell (bash in your case) and tells it to interpret:



sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


The arguments have been joined with spaces and all the SPC, ", =, &, @ characters, but not $ have been escaped. Because those $ have not been escaped that bash login shell will expand $0,$1, $2 but not $@ because of that backslash.



You can see it with:



$ SHELL=echo sudo -s sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
-c sh -c echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@" sh 1 2


So that login bash shell ends up running sh with -c as first argument,



echo "p0=-bash" && echo "p1=" && echo "p2=" && echo "all=$@"


as second argument and sh, 1, 2 as 3rd, 4th and 5th.



To work around it, you can use $0 instead of $0, because sudo transforms it to $0 which prevents the login shell from expanding it to the content of its $0:



$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


Edit: history brings light to the reason behind it



more findings while looking at the code. The non-escaping of $ was apparently introduced in 2013 by this change. Which refers to bug#564 which refers to bug#413.



It looks like before that bug#413 was "fixed", sudo behaved as I expected.



That is:



sudo -i 'some shell code'


Had the login shell interpret that shell code (and sudo -s 'some shell code' had $SHELL interpret that shell code). But the bug#413 resolution broke it, because the person who reported the bug didn't understand it worked that way. And the bug#564 broke it further (in an attempt to revert only part of the breakage introduced by the bug#413 resolution).



I went as far as compiling sudo 1.7.1 from 2009 and the -s/-i options were working as I'd expect then. 1.7.3 (with bug#413 resolution) worked kind of like you apparently expected:



$ sudo-1.7.1 -i 'echo "$SHELL"'
/bin/bash
$ sudo-1.7.3 -i 'echo "$SHELL"'
-bash: echo "$SHELL": command not found
$ sudo-1.8.21p2 -i 'echo "$SHELL"'
-bash: echo "/bin/bash": No such file or directory


$ sudo-1.7.3 -i sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2


That escaping introduced in the fix for that bug#413 is easily fooled because banging a in front of all bytes (not character) doesn't work for all.



Beside the obvious case of rc where is not even a quoting operator, in most shells, newline can't be quoted with :



$ SHELL=sh sudo -s echo $'anb'
ab
$ SHELL=csh sudo -s echo $'anb'
a b


It also means empty arguments are discarded:



$ sudo printf '<%s>n' a '' b
<a>
<>
<b>
$ sudo -s printf '<%s>n' a '' b
<a>
<b>


Also, by inserting a before every byte, it's transforming characters into others in charsets where byte 0x5c (the encoding of ) is found in other characters:



$ SHELL=bash LC_ALL=zh_HK.big5hkscs sudo -s echo $'xa3``uname`xa3`'
bash: line 2: Linuxα: command not found
α


0xa3 0x60 is Greek Epsilon in that locale. And sudo changes the 0x60s to 0x5c 0x60 and 0xa3 0x5c is Greek Alpha, which explains why the uname command was run. sudo changed the



ε`uname`ε


to



α``uname`α`


And of course the behaviour ends up being surprising in that the $-, $$, positional parameters and $varname (where varname is a valid POSIX variable name) are expanded but not the other parameters (like $@ here, but also $!, $?, $*, $#) or $varname or $var[1] (csh/tcsh/zsh) or $var(1) (rc/es/...).







share|improve this answer














share|improve this answer



share|improve this answer








edited Aug 17 at 6:46

























answered Aug 16 at 20:22









Stéphane ChazelasStéphane Chazelas

332k58 gold badges648 silver badges1018 bronze badges




332k58 gold badges648 silver badges1018 bronze badges















  • Ow, that's horrible. What do they propose to "fix" with quoting the arguments?

    – Kusalananda
    Aug 16 at 20:29











  • Answer accepted, in the spirit of "sudo does it in a very strange way". I do not guarantee that sudo on my system does everything written (or implied?) in this answer, as per my comment "I was rather hoping to avoid quoting messes :-)".

    – sourcejedi
    Aug 16 at 20:30






  • 1





    @Kusalananda, I never understood why sudo handled -s/--login that way. To me, it makes it pointless.

    – Stéphane Chazelas
    Aug 16 at 20:31











  • @Kusalananda, see edit with the historical context that explains the current mess.

    – Stéphane Chazelas
    Aug 16 at 21:23

















  • Ow, that's horrible. What do they propose to "fix" with quoting the arguments?

    – Kusalananda
    Aug 16 at 20:29











  • Answer accepted, in the spirit of "sudo does it in a very strange way". I do not guarantee that sudo on my system does everything written (or implied?) in this answer, as per my comment "I was rather hoping to avoid quoting messes :-)".

    – sourcejedi
    Aug 16 at 20:30






  • 1





    @Kusalananda, I never understood why sudo handled -s/--login that way. To me, it makes it pointless.

    – Stéphane Chazelas
    Aug 16 at 20:31











  • @Kusalananda, see edit with the historical context that explains the current mess.

    – Stéphane Chazelas
    Aug 16 at 21:23
















Ow, that's horrible. What do they propose to "fix" with quoting the arguments?

– Kusalananda
Aug 16 at 20:29





Ow, that's horrible. What do they propose to "fix" with quoting the arguments?

– Kusalananda
Aug 16 at 20:29













Answer accepted, in the spirit of "sudo does it in a very strange way". I do not guarantee that sudo on my system does everything written (or implied?) in this answer, as per my comment "I was rather hoping to avoid quoting messes :-)".

– sourcejedi
Aug 16 at 20:30





Answer accepted, in the spirit of "sudo does it in a very strange way". I do not guarantee that sudo on my system does everything written (or implied?) in this answer, as per my comment "I was rather hoping to avoid quoting messes :-)".

– sourcejedi
Aug 16 at 20:30




1




1





@Kusalananda, I never understood why sudo handled -s/--login that way. To me, it makes it pointless.

– Stéphane Chazelas
Aug 16 at 20:31





@Kusalananda, I never understood why sudo handled -s/--login that way. To me, it makes it pointless.

– Stéphane Chazelas
Aug 16 at 20:31













@Kusalananda, see edit with the historical context that explains the current mess.

– Stéphane Chazelas
Aug 16 at 21:23





@Kusalananda, see edit with the historical context that explains the current mess.

– Stéphane Chazelas
Aug 16 at 21:23

















draft saved

draft discarded
















































Thanks for contributing an answer to Unix & Linux Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f535925%2fsh-c-does-not-expand-positional-parameters-if-i-run-it-from-sudo-login%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Get product attribute by attribute group code in magento 2get product attribute by product attribute group in magento 2Magento 2 Log Bundle Product Data in List Page?How to get all product attribute of a attribute group of Default attribute set?Magento 2.1 Create a filter in the product grid by new attributeMagento 2 : Get Product Attribute values By GroupMagento 2 How to get all existing values for one attributeMagento 2 get custom attribute of a single product inside a pluginMagento 2.3 How to get all the Multi Source Inventory (MSI) locations collection in custom module?Magento2: how to develop rest API to get new productsGet product attribute by attribute group code ( [attribute_group_code] ) in magento 2

Category:9 (number) SubcategoriesMedia in category "9 (number)"Navigation menuUpload mediaGND ID: 4485639-8Library of Congress authority ID: sh85091979ReasonatorScholiaStatistics

Magento 2.3: How do i solve this, Not registered handle, on custom form?How can i rewrite TierPrice Block in Magento2magento 2 captcha not rendering if I override layout xmlmain.CRITICAL: Plugin class doesn't existMagento 2 : Problem while adding custom button order view page?Magento 2.2.5: Overriding Admin Controller sales/orderMagento 2.2.5: Add, Update and Delete existing products Custom OptionsMagento 2.3 : File Upload issue in UI Component FormMagento2 Not registered handleHow to configured Form Builder Js in my custom magento 2.3.0 module?Magento 2.3. How to create image upload field in an admin form