“sudo -s ” runs command in a shell, but wildcards or metacharacters not working











up vote
3
down vote

favorite












When using sudo -s (short for the "--shell" option), it is possible to pass "sudo" a command, in which case it will run the command in a shell started by "sudo" as the target user.



(Similarly, sudo -i, also available as the "--login" option, also starts a shell and similarly accepts a command, which behaves the same way.)



Running commands in sudo via a shell can be important in many cases:




  • When using wildcards under a directory the current user doesn't have access to, but root does, the command needs to run under a root shell to have the wildcards properly expanded.

  • Running a whole pipeline, many commands chained in pipes (|).

  • When running a shell built-ins, such as for, if, etc. Running a small whole inline "script" under a single sudo can be useful.


The documentation of the "-s" option says (emphasis mine):




Run the shell specified by the SHELL environment variable if it is set or the shell specified by the invoking user's password database entry. If a command is specified, it is passed to the shell for execution via the shell's -c option. If no command is specified, an interactive shell is executed. Note that most shells behave differently when a command is specified as compared to an interactive session; consult the shell's manual for details.




In other words, when passing sudo -s a command, it is passed to the shell using the -c option, which takes a string with a "script" and then proceeds to execute it as a shell script.



The documentation doesn't really go much further on how to use this option, or to present examples, except to say "consult the shell's manual for details." This implies the command received is passed directly to the shell's -c option. However, as it turns out, that is not the case.



Passing it a shell script with multiple words fails:



$ sudo -s 'ls -ld /var/empty'
/bin/bash: ls -ld /var/empty: No such file or directory


The error message implies it's trying to run the whole string as a simple command... Hmmm, ok, so maybe add spaces would work? Yes that's the case:



$ sudo -s ls -ld /var/empty
drwxr-xr-x. 3 root root 18 Jul 12 21:48 /var/empty


That's not really how the shell's -c works though... Oh well, let's try to use some metacharacters, such as ~, which is a shortcut for the home directory, to see how this behaves. Note the ~ needs to be quoted, to prevent the non-sudo shell from expanding it (in which case it would expand to the home of the non-root user, rather than /root which is expected):



$ sudo -s ls '~'
ls: cannot access '~': No such file or directory


Ok, so that doesn't work, and the error output seems to imply the expansion is not happening, since it's preserving a literal ~ there.



What about wildcards? Not working either:



$ sudo -s ls '/root/*.cfg'
ls: cannot access '/root/*.cfg': No such file or directory


In both these cases, running the command with $SHELL -c works just fine. In this case, $SHELL is bash, so:



$ sudo bash -c 'ls ~'
anaconda-ks.cfg
$ sudo bash -c 'ls /root/*.cfg'
/root/anaconda-ks.cfg


One exception is that variables seem to work on sudo -s, such as:



$ sudo -s echo '$HOME'
/root


So:




  • What is going on here?

  • Why do wildcards and metacharacters such as ~ do not work on the command passed to sudo -s or sudo -i?

  • Given $SHELL -c takes a single string with a script, but sudo -s takes multiple arguments, how is the script assembled from the arguments?

  • What is a reliable way to run commands on a shell via sudo?










share|improve this question
























  • Possible duplicate of How to use shell wildcards with sudo?
    – Ipor Sircer
    Nov 27 at 4:51






  • 1




    @IporSircer That question does not explain why sudo -s <command> does not work... The motivation for this question was this answer in which I suggested using sudo -s, but it turns out that doesn't work. I searched for it, and didn't find any other questions addressing why that doesn't work, so I decided to document it.
    – Filipe Brandenburger
    Nov 27 at 5:00















up vote
3
down vote

favorite












When using sudo -s (short for the "--shell" option), it is possible to pass "sudo" a command, in which case it will run the command in a shell started by "sudo" as the target user.



(Similarly, sudo -i, also available as the "--login" option, also starts a shell and similarly accepts a command, which behaves the same way.)



Running commands in sudo via a shell can be important in many cases:




  • When using wildcards under a directory the current user doesn't have access to, but root does, the command needs to run under a root shell to have the wildcards properly expanded.

  • Running a whole pipeline, many commands chained in pipes (|).

  • When running a shell built-ins, such as for, if, etc. Running a small whole inline "script" under a single sudo can be useful.


The documentation of the "-s" option says (emphasis mine):




Run the shell specified by the SHELL environment variable if it is set or the shell specified by the invoking user's password database entry. If a command is specified, it is passed to the shell for execution via the shell's -c option. If no command is specified, an interactive shell is executed. Note that most shells behave differently when a command is specified as compared to an interactive session; consult the shell's manual for details.




In other words, when passing sudo -s a command, it is passed to the shell using the -c option, which takes a string with a "script" and then proceeds to execute it as a shell script.



The documentation doesn't really go much further on how to use this option, or to present examples, except to say "consult the shell's manual for details." This implies the command received is passed directly to the shell's -c option. However, as it turns out, that is not the case.



Passing it a shell script with multiple words fails:



$ sudo -s 'ls -ld /var/empty'
/bin/bash: ls -ld /var/empty: No such file or directory


The error message implies it's trying to run the whole string as a simple command... Hmmm, ok, so maybe add spaces would work? Yes that's the case:



$ sudo -s ls -ld /var/empty
drwxr-xr-x. 3 root root 18 Jul 12 21:48 /var/empty


That's not really how the shell's -c works though... Oh well, let's try to use some metacharacters, such as ~, which is a shortcut for the home directory, to see how this behaves. Note the ~ needs to be quoted, to prevent the non-sudo shell from expanding it (in which case it would expand to the home of the non-root user, rather than /root which is expected):



$ sudo -s ls '~'
ls: cannot access '~': No such file or directory


Ok, so that doesn't work, and the error output seems to imply the expansion is not happening, since it's preserving a literal ~ there.



What about wildcards? Not working either:



$ sudo -s ls '/root/*.cfg'
ls: cannot access '/root/*.cfg': No such file or directory


In both these cases, running the command with $SHELL -c works just fine. In this case, $SHELL is bash, so:



$ sudo bash -c 'ls ~'
anaconda-ks.cfg
$ sudo bash -c 'ls /root/*.cfg'
/root/anaconda-ks.cfg


One exception is that variables seem to work on sudo -s, such as:



$ sudo -s echo '$HOME'
/root


So:




  • What is going on here?

  • Why do wildcards and metacharacters such as ~ do not work on the command passed to sudo -s or sudo -i?

  • Given $SHELL -c takes a single string with a script, but sudo -s takes multiple arguments, how is the script assembled from the arguments?

  • What is a reliable way to run commands on a shell via sudo?










share|improve this question
























  • Possible duplicate of How to use shell wildcards with sudo?
    – Ipor Sircer
    Nov 27 at 4:51






  • 1




    @IporSircer That question does not explain why sudo -s <command> does not work... The motivation for this question was this answer in which I suggested using sudo -s, but it turns out that doesn't work. I searched for it, and didn't find any other questions addressing why that doesn't work, so I decided to document it.
    – Filipe Brandenburger
    Nov 27 at 5:00













up vote
3
down vote

favorite









up vote
3
down vote

favorite











When using sudo -s (short for the "--shell" option), it is possible to pass "sudo" a command, in which case it will run the command in a shell started by "sudo" as the target user.



(Similarly, sudo -i, also available as the "--login" option, also starts a shell and similarly accepts a command, which behaves the same way.)



Running commands in sudo via a shell can be important in many cases:




  • When using wildcards under a directory the current user doesn't have access to, but root does, the command needs to run under a root shell to have the wildcards properly expanded.

  • Running a whole pipeline, many commands chained in pipes (|).

  • When running a shell built-ins, such as for, if, etc. Running a small whole inline "script" under a single sudo can be useful.


The documentation of the "-s" option says (emphasis mine):




Run the shell specified by the SHELL environment variable if it is set or the shell specified by the invoking user's password database entry. If a command is specified, it is passed to the shell for execution via the shell's -c option. If no command is specified, an interactive shell is executed. Note that most shells behave differently when a command is specified as compared to an interactive session; consult the shell's manual for details.




In other words, when passing sudo -s a command, it is passed to the shell using the -c option, which takes a string with a "script" and then proceeds to execute it as a shell script.



The documentation doesn't really go much further on how to use this option, or to present examples, except to say "consult the shell's manual for details." This implies the command received is passed directly to the shell's -c option. However, as it turns out, that is not the case.



Passing it a shell script with multiple words fails:



$ sudo -s 'ls -ld /var/empty'
/bin/bash: ls -ld /var/empty: No such file or directory


The error message implies it's trying to run the whole string as a simple command... Hmmm, ok, so maybe add spaces would work? Yes that's the case:



$ sudo -s ls -ld /var/empty
drwxr-xr-x. 3 root root 18 Jul 12 21:48 /var/empty


That's not really how the shell's -c works though... Oh well, let's try to use some metacharacters, such as ~, which is a shortcut for the home directory, to see how this behaves. Note the ~ needs to be quoted, to prevent the non-sudo shell from expanding it (in which case it would expand to the home of the non-root user, rather than /root which is expected):



$ sudo -s ls '~'
ls: cannot access '~': No such file or directory


Ok, so that doesn't work, and the error output seems to imply the expansion is not happening, since it's preserving a literal ~ there.



What about wildcards? Not working either:



$ sudo -s ls '/root/*.cfg'
ls: cannot access '/root/*.cfg': No such file or directory


In both these cases, running the command with $SHELL -c works just fine. In this case, $SHELL is bash, so:



$ sudo bash -c 'ls ~'
anaconda-ks.cfg
$ sudo bash -c 'ls /root/*.cfg'
/root/anaconda-ks.cfg


One exception is that variables seem to work on sudo -s, such as:



$ sudo -s echo '$HOME'
/root


So:




  • What is going on here?

  • Why do wildcards and metacharacters such as ~ do not work on the command passed to sudo -s or sudo -i?

  • Given $SHELL -c takes a single string with a script, but sudo -s takes multiple arguments, how is the script assembled from the arguments?

  • What is a reliable way to run commands on a shell via sudo?










share|improve this question















When using sudo -s (short for the "--shell" option), it is possible to pass "sudo" a command, in which case it will run the command in a shell started by "sudo" as the target user.



(Similarly, sudo -i, also available as the "--login" option, also starts a shell and similarly accepts a command, which behaves the same way.)



Running commands in sudo via a shell can be important in many cases:




  • When using wildcards under a directory the current user doesn't have access to, but root does, the command needs to run under a root shell to have the wildcards properly expanded.

  • Running a whole pipeline, many commands chained in pipes (|).

  • When running a shell built-ins, such as for, if, etc. Running a small whole inline "script" under a single sudo can be useful.


The documentation of the "-s" option says (emphasis mine):




Run the shell specified by the SHELL environment variable if it is set or the shell specified by the invoking user's password database entry. If a command is specified, it is passed to the shell for execution via the shell's -c option. If no command is specified, an interactive shell is executed. Note that most shells behave differently when a command is specified as compared to an interactive session; consult the shell's manual for details.




In other words, when passing sudo -s a command, it is passed to the shell using the -c option, which takes a string with a "script" and then proceeds to execute it as a shell script.



The documentation doesn't really go much further on how to use this option, or to present examples, except to say "consult the shell's manual for details." This implies the command received is passed directly to the shell's -c option. However, as it turns out, that is not the case.



Passing it a shell script with multiple words fails:



$ sudo -s 'ls -ld /var/empty'
/bin/bash: ls -ld /var/empty: No such file or directory


The error message implies it's trying to run the whole string as a simple command... Hmmm, ok, so maybe add spaces would work? Yes that's the case:



$ sudo -s ls -ld /var/empty
drwxr-xr-x. 3 root root 18 Jul 12 21:48 /var/empty


That's not really how the shell's -c works though... Oh well, let's try to use some metacharacters, such as ~, which is a shortcut for the home directory, to see how this behaves. Note the ~ needs to be quoted, to prevent the non-sudo shell from expanding it (in which case it would expand to the home of the non-root user, rather than /root which is expected):



$ sudo -s ls '~'
ls: cannot access '~': No such file or directory


Ok, so that doesn't work, and the error output seems to imply the expansion is not happening, since it's preserving a literal ~ there.



What about wildcards? Not working either:



$ sudo -s ls '/root/*.cfg'
ls: cannot access '/root/*.cfg': No such file or directory


In both these cases, running the command with $SHELL -c works just fine. In this case, $SHELL is bash, so:



$ sudo bash -c 'ls ~'
anaconda-ks.cfg
$ sudo bash -c 'ls /root/*.cfg'
/root/anaconda-ks.cfg


One exception is that variables seem to work on sudo -s, such as:



$ sudo -s echo '$HOME'
/root


So:




  • What is going on here?

  • Why do wildcards and metacharacters such as ~ do not work on the command passed to sudo -s or sudo -i?

  • Given $SHELL -c takes a single string with a script, but sudo -s takes multiple arguments, how is the script assembled from the arguments?

  • What is a reliable way to run commands on a shell via sudo?







shell sudo






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 2 days ago

























asked Nov 27 at 4:48









Filipe Brandenburger

6,6701732




6,6701732












  • Possible duplicate of How to use shell wildcards with sudo?
    – Ipor Sircer
    Nov 27 at 4:51






  • 1




    @IporSircer That question does not explain why sudo -s <command> does not work... The motivation for this question was this answer in which I suggested using sudo -s, but it turns out that doesn't work. I searched for it, and didn't find any other questions addressing why that doesn't work, so I decided to document it.
    – Filipe Brandenburger
    Nov 27 at 5:00


















  • Possible duplicate of How to use shell wildcards with sudo?
    – Ipor Sircer
    Nov 27 at 4:51






  • 1




    @IporSircer That question does not explain why sudo -s <command> does not work... The motivation for this question was this answer in which I suggested using sudo -s, but it turns out that doesn't work. I searched for it, and didn't find any other questions addressing why that doesn't work, so I decided to document it.
    – Filipe Brandenburger
    Nov 27 at 5:00
















Possible duplicate of How to use shell wildcards with sudo?
– Ipor Sircer
Nov 27 at 4:51




Possible duplicate of How to use shell wildcards with sudo?
– Ipor Sircer
Nov 27 at 4:51




1




1




@IporSircer That question does not explain why sudo -s <command> does not work... The motivation for this question was this answer in which I suggested using sudo -s, but it turns out that doesn't work. I searched for it, and didn't find any other questions addressing why that doesn't work, so I decided to document it.
– Filipe Brandenburger
Nov 27 at 5:00




@IporSircer That question does not explain why sudo -s <command> does not work... The motivation for this question was this answer in which I suggested using sudo -s, but it turns out that doesn't work. I searched for it, and didn't find any other questions addressing why that doesn't work, so I decided to document it.
– Filipe Brandenburger
Nov 27 at 5:00










1 Answer
1






active

oldest

votes

















up vote
4
down vote



accepted










TL;DR: When taking a command on the "--shell" or "--login" options, sudo will escape most characters (using backslash escapes), including all metacharacters (except for $), also including spaces.



That breaks about every use case where you'd want to use a shell, which makes sudo -s mostly unsuitable to run shell commands that need to run as shell commands.



Instead of sudo -s, use sudo sh -c '...' or sudo bash -c '...' (or whatever the expected $SHELL is.) Instead of sudo -i, use sudo bash -l -c '...' instead (assuming root's shell is bash again.)





Looking at the relevant part of the sudo source code, there's this snippet:



/* quote potential meta characters */
if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\';
*dst++ = *src;


The snippet runs over each character of each argument. If the character is not alphanumeric, underscore, dash or dollar sign, it will be escaped with a backslash.



This means wildcards *, ?, [...], etc. will be escaped to *, ?, [...].



Also metacharacters such as ~, ;, |, etc. will be escaped to ~, ;, |.



A string with spaces ls /root will be escaped to ls /root.



For some reason, $ is an exception in escaping, that's why sudo -s echo '$HOME' works, since the $ will be kept unescaped. (Note this only works for variables, and not even the ${HOME} form, in which case the curly braces will be escaped, which will break the expression.)





The consequences of this quoting is that most cases that require running a shell as root can't really benefit from the sudo -s <command> format:




  • Wilcards won't be expanded, since they'll be escaped and won't work as wildcards.

  • Pipelines won't work, since the pipe | will be escaped and won't work either.

  • Commands like for and if typically need to use ;, which will be escaped and won't work either. A newline would be an alternative, but there doesn't seem to be a way to introduce an unescaped newline either.


In short, the form sudo -s <command> only seems to work for cases that don't involve wildcards, pipelines, scriptlets, etc. But then, you typically don't really need a shell to run those commands! Just running sudo <command> without the -s is typically enough for those cases.



It's unclear what the motivation for the sudo implementation. Perhaps to maintain some consistency between handling of commands when adding or removing the -s. Perhaps also because -i precedes -s and in that case there's some small difference in how environment variables are set...





WORKAROUND: The workaround is to run the shell's -c explicitly.



This involves knowing what $SHELL is for the target user (which might not match the current user, so using $SHELL directly is not always correct.)



For instance, instead of sudo -s ls -l '/root/*.cfg', use:



$ sudo bash -c 'ls -l /root/*.cfg'


And instead of sudo -i ls -l '~', use:



$ sudo bash -l -c 'ls -l ~'


(bash's -l argument creates a "login" shell, which is equivalent to the one created by sudo -i.)






share|improve this answer























    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',
    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%2f484362%2fsudo-s-command-runs-command-in-a-shell-but-wildcards-or-metacharacters-not%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








    up vote
    4
    down vote



    accepted










    TL;DR: When taking a command on the "--shell" or "--login" options, sudo will escape most characters (using backslash escapes), including all metacharacters (except for $), also including spaces.



    That breaks about every use case where you'd want to use a shell, which makes sudo -s mostly unsuitable to run shell commands that need to run as shell commands.



    Instead of sudo -s, use sudo sh -c '...' or sudo bash -c '...' (or whatever the expected $SHELL is.) Instead of sudo -i, use sudo bash -l -c '...' instead (assuming root's shell is bash again.)





    Looking at the relevant part of the sudo source code, there's this snippet:



    /* quote potential meta characters */
    if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
    *dst++ = '\';
    *dst++ = *src;


    The snippet runs over each character of each argument. If the character is not alphanumeric, underscore, dash or dollar sign, it will be escaped with a backslash.



    This means wildcards *, ?, [...], etc. will be escaped to *, ?, [...].



    Also metacharacters such as ~, ;, |, etc. will be escaped to ~, ;, |.



    A string with spaces ls /root will be escaped to ls /root.



    For some reason, $ is an exception in escaping, that's why sudo -s echo '$HOME' works, since the $ will be kept unescaped. (Note this only works for variables, and not even the ${HOME} form, in which case the curly braces will be escaped, which will break the expression.)





    The consequences of this quoting is that most cases that require running a shell as root can't really benefit from the sudo -s <command> format:




    • Wilcards won't be expanded, since they'll be escaped and won't work as wildcards.

    • Pipelines won't work, since the pipe | will be escaped and won't work either.

    • Commands like for and if typically need to use ;, which will be escaped and won't work either. A newline would be an alternative, but there doesn't seem to be a way to introduce an unescaped newline either.


    In short, the form sudo -s <command> only seems to work for cases that don't involve wildcards, pipelines, scriptlets, etc. But then, you typically don't really need a shell to run those commands! Just running sudo <command> without the -s is typically enough for those cases.



    It's unclear what the motivation for the sudo implementation. Perhaps to maintain some consistency between handling of commands when adding or removing the -s. Perhaps also because -i precedes -s and in that case there's some small difference in how environment variables are set...





    WORKAROUND: The workaround is to run the shell's -c explicitly.



    This involves knowing what $SHELL is for the target user (which might not match the current user, so using $SHELL directly is not always correct.)



    For instance, instead of sudo -s ls -l '/root/*.cfg', use:



    $ sudo bash -c 'ls -l /root/*.cfg'


    And instead of sudo -i ls -l '~', use:



    $ sudo bash -l -c 'ls -l ~'


    (bash's -l argument creates a "login" shell, which is equivalent to the one created by sudo -i.)






    share|improve this answer



























      up vote
      4
      down vote



      accepted










      TL;DR: When taking a command on the "--shell" or "--login" options, sudo will escape most characters (using backslash escapes), including all metacharacters (except for $), also including spaces.



      That breaks about every use case where you'd want to use a shell, which makes sudo -s mostly unsuitable to run shell commands that need to run as shell commands.



      Instead of sudo -s, use sudo sh -c '...' or sudo bash -c '...' (or whatever the expected $SHELL is.) Instead of sudo -i, use sudo bash -l -c '...' instead (assuming root's shell is bash again.)





      Looking at the relevant part of the sudo source code, there's this snippet:



      /* quote potential meta characters */
      if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
      *dst++ = '\';
      *dst++ = *src;


      The snippet runs over each character of each argument. If the character is not alphanumeric, underscore, dash or dollar sign, it will be escaped with a backslash.



      This means wildcards *, ?, [...], etc. will be escaped to *, ?, [...].



      Also metacharacters such as ~, ;, |, etc. will be escaped to ~, ;, |.



      A string with spaces ls /root will be escaped to ls /root.



      For some reason, $ is an exception in escaping, that's why sudo -s echo '$HOME' works, since the $ will be kept unescaped. (Note this only works for variables, and not even the ${HOME} form, in which case the curly braces will be escaped, which will break the expression.)





      The consequences of this quoting is that most cases that require running a shell as root can't really benefit from the sudo -s <command> format:




      • Wilcards won't be expanded, since they'll be escaped and won't work as wildcards.

      • Pipelines won't work, since the pipe | will be escaped and won't work either.

      • Commands like for and if typically need to use ;, which will be escaped and won't work either. A newline would be an alternative, but there doesn't seem to be a way to introduce an unescaped newline either.


      In short, the form sudo -s <command> only seems to work for cases that don't involve wildcards, pipelines, scriptlets, etc. But then, you typically don't really need a shell to run those commands! Just running sudo <command> without the -s is typically enough for those cases.



      It's unclear what the motivation for the sudo implementation. Perhaps to maintain some consistency between handling of commands when adding or removing the -s. Perhaps also because -i precedes -s and in that case there's some small difference in how environment variables are set...





      WORKAROUND: The workaround is to run the shell's -c explicitly.



      This involves knowing what $SHELL is for the target user (which might not match the current user, so using $SHELL directly is not always correct.)



      For instance, instead of sudo -s ls -l '/root/*.cfg', use:



      $ sudo bash -c 'ls -l /root/*.cfg'


      And instead of sudo -i ls -l '~', use:



      $ sudo bash -l -c 'ls -l ~'


      (bash's -l argument creates a "login" shell, which is equivalent to the one created by sudo -i.)






      share|improve this answer

























        up vote
        4
        down vote



        accepted







        up vote
        4
        down vote



        accepted






        TL;DR: When taking a command on the "--shell" or "--login" options, sudo will escape most characters (using backslash escapes), including all metacharacters (except for $), also including spaces.



        That breaks about every use case where you'd want to use a shell, which makes sudo -s mostly unsuitable to run shell commands that need to run as shell commands.



        Instead of sudo -s, use sudo sh -c '...' or sudo bash -c '...' (or whatever the expected $SHELL is.) Instead of sudo -i, use sudo bash -l -c '...' instead (assuming root's shell is bash again.)





        Looking at the relevant part of the sudo source code, there's this snippet:



        /* quote potential meta characters */
        if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
        *dst++ = '\';
        *dst++ = *src;


        The snippet runs over each character of each argument. If the character is not alphanumeric, underscore, dash or dollar sign, it will be escaped with a backslash.



        This means wildcards *, ?, [...], etc. will be escaped to *, ?, [...].



        Also metacharacters such as ~, ;, |, etc. will be escaped to ~, ;, |.



        A string with spaces ls /root will be escaped to ls /root.



        For some reason, $ is an exception in escaping, that's why sudo -s echo '$HOME' works, since the $ will be kept unescaped. (Note this only works for variables, and not even the ${HOME} form, in which case the curly braces will be escaped, which will break the expression.)





        The consequences of this quoting is that most cases that require running a shell as root can't really benefit from the sudo -s <command> format:




        • Wilcards won't be expanded, since they'll be escaped and won't work as wildcards.

        • Pipelines won't work, since the pipe | will be escaped and won't work either.

        • Commands like for and if typically need to use ;, which will be escaped and won't work either. A newline would be an alternative, but there doesn't seem to be a way to introduce an unescaped newline either.


        In short, the form sudo -s <command> only seems to work for cases that don't involve wildcards, pipelines, scriptlets, etc. But then, you typically don't really need a shell to run those commands! Just running sudo <command> without the -s is typically enough for those cases.



        It's unclear what the motivation for the sudo implementation. Perhaps to maintain some consistency between handling of commands when adding or removing the -s. Perhaps also because -i precedes -s and in that case there's some small difference in how environment variables are set...





        WORKAROUND: The workaround is to run the shell's -c explicitly.



        This involves knowing what $SHELL is for the target user (which might not match the current user, so using $SHELL directly is not always correct.)



        For instance, instead of sudo -s ls -l '/root/*.cfg', use:



        $ sudo bash -c 'ls -l /root/*.cfg'


        And instead of sudo -i ls -l '~', use:



        $ sudo bash -l -c 'ls -l ~'


        (bash's -l argument creates a "login" shell, which is equivalent to the one created by sudo -i.)






        share|improve this answer














        TL;DR: When taking a command on the "--shell" or "--login" options, sudo will escape most characters (using backslash escapes), including all metacharacters (except for $), also including spaces.



        That breaks about every use case where you'd want to use a shell, which makes sudo -s mostly unsuitable to run shell commands that need to run as shell commands.



        Instead of sudo -s, use sudo sh -c '...' or sudo bash -c '...' (or whatever the expected $SHELL is.) Instead of sudo -i, use sudo bash -l -c '...' instead (assuming root's shell is bash again.)





        Looking at the relevant part of the sudo source code, there's this snippet:



        /* quote potential meta characters */
        if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
        *dst++ = '\';
        *dst++ = *src;


        The snippet runs over each character of each argument. If the character is not alphanumeric, underscore, dash or dollar sign, it will be escaped with a backslash.



        This means wildcards *, ?, [...], etc. will be escaped to *, ?, [...].



        Also metacharacters such as ~, ;, |, etc. will be escaped to ~, ;, |.



        A string with spaces ls /root will be escaped to ls /root.



        For some reason, $ is an exception in escaping, that's why sudo -s echo '$HOME' works, since the $ will be kept unescaped. (Note this only works for variables, and not even the ${HOME} form, in which case the curly braces will be escaped, which will break the expression.)





        The consequences of this quoting is that most cases that require running a shell as root can't really benefit from the sudo -s <command> format:




        • Wilcards won't be expanded, since they'll be escaped and won't work as wildcards.

        • Pipelines won't work, since the pipe | will be escaped and won't work either.

        • Commands like for and if typically need to use ;, which will be escaped and won't work either. A newline would be an alternative, but there doesn't seem to be a way to introduce an unescaped newline either.


        In short, the form sudo -s <command> only seems to work for cases that don't involve wildcards, pipelines, scriptlets, etc. But then, you typically don't really need a shell to run those commands! Just running sudo <command> without the -s is typically enough for those cases.



        It's unclear what the motivation for the sudo implementation. Perhaps to maintain some consistency between handling of commands when adding or removing the -s. Perhaps also because -i precedes -s and in that case there's some small difference in how environment variables are set...





        WORKAROUND: The workaround is to run the shell's -c explicitly.



        This involves knowing what $SHELL is for the target user (which might not match the current user, so using $SHELL directly is not always correct.)



        For instance, instead of sudo -s ls -l '/root/*.cfg', use:



        $ sudo bash -c 'ls -l /root/*.cfg'


        And instead of sudo -i ls -l '~', use:



        $ sudo bash -l -c 'ls -l ~'


        (bash's -l argument creates a "login" shell, which is equivalent to the one created by sudo -i.)







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 27 at 5:16

























        answered Nov 27 at 4:48









        Filipe Brandenburger

        6,6701732




        6,6701732






























            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.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • 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%2f484362%2fsudo-s-command-runs-command-in-a-shell-but-wildcards-or-metacharacters-not%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

            Accessing regular linux commands in Huawei's Dopra Linux

            Can't connect RFCOMM socket: Host is down

            Kernel panic - not syncing: Fatal Exception in Interrupt