Executing `sh -c` script through SSH (passing arguments safely and sanely)

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
3
down vote

favorite












I suddenly realized I don't know how to execute things over SSH.



I tried to do



$ ssh user@server sh -c 'echo "hello"'


but it outputs nothing, or rather, it outputs an empty line. If the command given to ssh is run through $SHELL -c on the remote host, then I can see why that is (or I can try to rationalize it to myself anyway).



Ok, second try:



$ ssh user@server 'echo "hello"'
hello


All well and good.



Now for what I was really hoping would work:



$ ssh user@server 'echo "hello $1"' sh "world"
hello sh world


Hmm... where does the sh come from? This is indicating that $1 is empty and that what really gets executed on the other side is something like



$SHELL -c 'echo "hello sh world"'


and not what I had hoped,



$SHELL -c 'echo "hello $1"' sh "world"


Is there a way to safely pass arguments to the script executed via ssh, in a sane and sensible way that is analogous to running



sh -c 'script text' sh "my arg1" "my arg2" "my arg3" ...


but on the remote host?



My login shell, both locally and remotely is /bin/sh.




Safely = Preserving whitespace etc. in arguments.



Sanely = No crazy escaping of quotes.







share|improve this question





















  • You should search Stephane question and answer, he has raised an excelent one.
    – cuonglm
    Jun 15 at 15:21










  • @cuonglm Are you referring to his answer to unix.stackexchange.com/questions/299037/pass-variable-in-ssh ? The issue is slightly different. I'm happy to have the arguments to the script expand locally, so I'm not looking to send variables over.
    – Kusalananda
    Jun 15 at 15:29






  • 2




    @cuonglm Ah, no, you mean unix.stackexchange.com/questions/205567/… Hmmm... That question is a lot more general. I have to think...
    – Kusalananda
    Jun 15 at 15:41










  • This looks to me like a duplicate or subset of How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user?
    – Stéphane Chazelas
    Jun 15 at 16:32










  • @StéphaneChazelas That may well be the case (I hadn't found it until now). Your answer there is slightly too generous for me to fully digest in one sitting though.
    – Kusalananda
    Jun 15 at 17:00














up vote
3
down vote

favorite












I suddenly realized I don't know how to execute things over SSH.



I tried to do



$ ssh user@server sh -c 'echo "hello"'


but it outputs nothing, or rather, it outputs an empty line. If the command given to ssh is run through $SHELL -c on the remote host, then I can see why that is (or I can try to rationalize it to myself anyway).



Ok, second try:



$ ssh user@server 'echo "hello"'
hello


All well and good.



Now for what I was really hoping would work:



$ ssh user@server 'echo "hello $1"' sh "world"
hello sh world


Hmm... where does the sh come from? This is indicating that $1 is empty and that what really gets executed on the other side is something like



$SHELL -c 'echo "hello sh world"'


and not what I had hoped,



$SHELL -c 'echo "hello $1"' sh "world"


Is there a way to safely pass arguments to the script executed via ssh, in a sane and sensible way that is analogous to running



sh -c 'script text' sh "my arg1" "my arg2" "my arg3" ...


but on the remote host?



My login shell, both locally and remotely is /bin/sh.




Safely = Preserving whitespace etc. in arguments.



Sanely = No crazy escaping of quotes.







share|improve this question





















  • You should search Stephane question and answer, he has raised an excelent one.
    – cuonglm
    Jun 15 at 15:21










  • @cuonglm Are you referring to his answer to unix.stackexchange.com/questions/299037/pass-variable-in-ssh ? The issue is slightly different. I'm happy to have the arguments to the script expand locally, so I'm not looking to send variables over.
    – Kusalananda
    Jun 15 at 15:29






  • 2




    @cuonglm Ah, no, you mean unix.stackexchange.com/questions/205567/… Hmmm... That question is a lot more general. I have to think...
    – Kusalananda
    Jun 15 at 15:41










  • This looks to me like a duplicate or subset of How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user?
    – Stéphane Chazelas
    Jun 15 at 16:32










  • @StéphaneChazelas That may well be the case (I hadn't found it until now). Your answer there is slightly too generous for me to fully digest in one sitting though.
    – Kusalananda
    Jun 15 at 17:00












up vote
3
down vote

favorite









up vote
3
down vote

favorite











I suddenly realized I don't know how to execute things over SSH.



I tried to do



$ ssh user@server sh -c 'echo "hello"'


but it outputs nothing, or rather, it outputs an empty line. If the command given to ssh is run through $SHELL -c on the remote host, then I can see why that is (or I can try to rationalize it to myself anyway).



Ok, second try:



$ ssh user@server 'echo "hello"'
hello


All well and good.



Now for what I was really hoping would work:



$ ssh user@server 'echo "hello $1"' sh "world"
hello sh world


Hmm... where does the sh come from? This is indicating that $1 is empty and that what really gets executed on the other side is something like



$SHELL -c 'echo "hello sh world"'


and not what I had hoped,



$SHELL -c 'echo "hello $1"' sh "world"


Is there a way to safely pass arguments to the script executed via ssh, in a sane and sensible way that is analogous to running



sh -c 'script text' sh "my arg1" "my arg2" "my arg3" ...


but on the remote host?



My login shell, both locally and remotely is /bin/sh.




Safely = Preserving whitespace etc. in arguments.



Sanely = No crazy escaping of quotes.







share|improve this question













I suddenly realized I don't know how to execute things over SSH.



I tried to do



$ ssh user@server sh -c 'echo "hello"'


but it outputs nothing, or rather, it outputs an empty line. If the command given to ssh is run through $SHELL -c on the remote host, then I can see why that is (or I can try to rationalize it to myself anyway).



Ok, second try:



$ ssh user@server 'echo "hello"'
hello


All well and good.



Now for what I was really hoping would work:



$ ssh user@server 'echo "hello $1"' sh "world"
hello sh world


Hmm... where does the sh come from? This is indicating that $1 is empty and that what really gets executed on the other side is something like



$SHELL -c 'echo "hello sh world"'


and not what I had hoped,



$SHELL -c 'echo "hello $1"' sh "world"


Is there a way to safely pass arguments to the script executed via ssh, in a sane and sensible way that is analogous to running



sh -c 'script text' sh "my arg1" "my arg2" "my arg3" ...


but on the remote host?



My login shell, both locally and remotely is /bin/sh.




Safely = Preserving whitespace etc. in arguments.



Sanely = No crazy escaping of quotes.









share|improve this question












share|improve this question




share|improve this question








edited Jun 15 at 15:33
























asked Jun 15 at 15:09









Kusalananda

101k13199312




101k13199312











  • You should search Stephane question and answer, he has raised an excelent one.
    – cuonglm
    Jun 15 at 15:21










  • @cuonglm Are you referring to his answer to unix.stackexchange.com/questions/299037/pass-variable-in-ssh ? The issue is slightly different. I'm happy to have the arguments to the script expand locally, so I'm not looking to send variables over.
    – Kusalananda
    Jun 15 at 15:29






  • 2




    @cuonglm Ah, no, you mean unix.stackexchange.com/questions/205567/… Hmmm... That question is a lot more general. I have to think...
    – Kusalananda
    Jun 15 at 15:41










  • This looks to me like a duplicate or subset of How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user?
    – Stéphane Chazelas
    Jun 15 at 16:32










  • @StéphaneChazelas That may well be the case (I hadn't found it until now). Your answer there is slightly too generous for me to fully digest in one sitting though.
    – Kusalananda
    Jun 15 at 17:00
















  • You should search Stephane question and answer, he has raised an excelent one.
    – cuonglm
    Jun 15 at 15:21










  • @cuonglm Are you referring to his answer to unix.stackexchange.com/questions/299037/pass-variable-in-ssh ? The issue is slightly different. I'm happy to have the arguments to the script expand locally, so I'm not looking to send variables over.
    – Kusalananda
    Jun 15 at 15:29






  • 2




    @cuonglm Ah, no, you mean unix.stackexchange.com/questions/205567/… Hmmm... That question is a lot more general. I have to think...
    – Kusalananda
    Jun 15 at 15:41










  • This looks to me like a duplicate or subset of How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user?
    – Stéphane Chazelas
    Jun 15 at 16:32










  • @StéphaneChazelas That may well be the case (I hadn't found it until now). Your answer there is slightly too generous for me to fully digest in one sitting though.
    – Kusalananda
    Jun 15 at 17:00















You should search Stephane question and answer, he has raised an excelent one.
– cuonglm
Jun 15 at 15:21




You should search Stephane question and answer, he has raised an excelent one.
– cuonglm
Jun 15 at 15:21












@cuonglm Are you referring to his answer to unix.stackexchange.com/questions/299037/pass-variable-in-ssh ? The issue is slightly different. I'm happy to have the arguments to the script expand locally, so I'm not looking to send variables over.
– Kusalananda
Jun 15 at 15:29




@cuonglm Are you referring to his answer to unix.stackexchange.com/questions/299037/pass-variable-in-ssh ? The issue is slightly different. I'm happy to have the arguments to the script expand locally, so I'm not looking to send variables over.
– Kusalananda
Jun 15 at 15:29




2




2




@cuonglm Ah, no, you mean unix.stackexchange.com/questions/205567/… Hmmm... That question is a lot more general. I have to think...
– Kusalananda
Jun 15 at 15:41




@cuonglm Ah, no, you mean unix.stackexchange.com/questions/205567/… Hmmm... That question is a lot more general. I have to think...
– Kusalananda
Jun 15 at 15:41












This looks to me like a duplicate or subset of How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user?
– Stéphane Chazelas
Jun 15 at 16:32




This looks to me like a duplicate or subset of How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user?
– Stéphane Chazelas
Jun 15 at 16:32












@StéphaneChazelas That may well be the case (I hadn't found it until now). Your answer there is slightly too generous for me to fully digest in one sitting though.
– Kusalananda
Jun 15 at 17:00




@StéphaneChazelas That may well be the case (I hadn't found it until now). Your answer there is slightly too generous for me to fully digest in one sitting though.
– Kusalananda
Jun 15 at 17:00










1 Answer
1






active

oldest

votes

















up vote
3
down vote



accepted










The first thing to understand in this process is how ssh handles its arguments. I don't mean the arguments to the thing you're trying to run, but arguments of ssh. When you invoke ssh, the arguments after the remote host specification (user@server) are concatenated together, and passed through the shell on the remote end. This is important to note, as just because your arguments are properly split on the local side, does not mean they will be properly split on the remote side.



To use your example:



ssh user@server 'echo "hello $1"' sh "world"


These arguments get concatenated as the command:



echo "hello $1" sh world


This is why you get



hello sh world


The double space between hello and sh is because that's where $1 was supposed to go, but there is no $1.



 



As another example, without the $1, is:



ssh user@server echo "foo bar" baz


Which results in the output:



foo bar baz


This is because the arguments are being concatenated together, so you end up with the command:



echo foo bar baz


 



Since there is no way to get around the command being passed through a shell, you just have to ensure that what you passed can survive the shell evaluation. The way I usually accomplish this is with printf "%q "



For example:



cmd=(echo "foo bar" baz)
ssh user@server "$(printf "%q " "$cmd[@]")"


Which results in the output:



foo bar baz


While it's cleaner and easier to understand with cmd being a separate var, it's not required. The following works just the same:



ssh user@server "$(printf "%q " echo "foo bar" baz)"


 



This also works fine with your shell argument example:



cmd=(sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3")
ssh user@server "$(printf "%q " "$cmd[@]")"


Which results in the output:



1=<my arg1> 2=<my arg2> 3=<my arg3>


 




As an alternative solution, you can pass your command as a complete shell script. For example:



ssh user@server <<'EOF'
sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3"
EOF


There are drawbacks to this solution though as it's harder to do programmatically (generating the doc to pass on STDIN). Also because you're using STDIN, if you want the script on the remote side to read STDIN, you can't (at least not without some trickery).






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: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    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%2f450020%2fexecuting-sh-c-script-through-ssh-passing-arguments-safely-and-sanely%23new-answer', 'question_page');

    );

    Post as a guest






























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    3
    down vote



    accepted










    The first thing to understand in this process is how ssh handles its arguments. I don't mean the arguments to the thing you're trying to run, but arguments of ssh. When you invoke ssh, the arguments after the remote host specification (user@server) are concatenated together, and passed through the shell on the remote end. This is important to note, as just because your arguments are properly split on the local side, does not mean they will be properly split on the remote side.



    To use your example:



    ssh user@server 'echo "hello $1"' sh "world"


    These arguments get concatenated as the command:



    echo "hello $1" sh world


    This is why you get



    hello sh world


    The double space between hello and sh is because that's where $1 was supposed to go, but there is no $1.



     



    As another example, without the $1, is:



    ssh user@server echo "foo bar" baz


    Which results in the output:



    foo bar baz


    This is because the arguments are being concatenated together, so you end up with the command:



    echo foo bar baz


     



    Since there is no way to get around the command being passed through a shell, you just have to ensure that what you passed can survive the shell evaluation. The way I usually accomplish this is with printf "%q "



    For example:



    cmd=(echo "foo bar" baz)
    ssh user@server "$(printf "%q " "$cmd[@]")"


    Which results in the output:



    foo bar baz


    While it's cleaner and easier to understand with cmd being a separate var, it's not required. The following works just the same:



    ssh user@server "$(printf "%q " echo "foo bar" baz)"


     



    This also works fine with your shell argument example:



    cmd=(sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3")
    ssh user@server "$(printf "%q " "$cmd[@]")"


    Which results in the output:



    1=<my arg1> 2=<my arg2> 3=<my arg3>


     




    As an alternative solution, you can pass your command as a complete shell script. For example:



    ssh user@server <<'EOF'
    sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3"
    EOF


    There are drawbacks to this solution though as it's harder to do programmatically (generating the doc to pass on STDIN). Also because you're using STDIN, if you want the script on the remote side to read STDIN, you can't (at least not without some trickery).






    share|improve this answer



























      up vote
      3
      down vote



      accepted










      The first thing to understand in this process is how ssh handles its arguments. I don't mean the arguments to the thing you're trying to run, but arguments of ssh. When you invoke ssh, the arguments after the remote host specification (user@server) are concatenated together, and passed through the shell on the remote end. This is important to note, as just because your arguments are properly split on the local side, does not mean they will be properly split on the remote side.



      To use your example:



      ssh user@server 'echo "hello $1"' sh "world"


      These arguments get concatenated as the command:



      echo "hello $1" sh world


      This is why you get



      hello sh world


      The double space between hello and sh is because that's where $1 was supposed to go, but there is no $1.



       



      As another example, without the $1, is:



      ssh user@server echo "foo bar" baz


      Which results in the output:



      foo bar baz


      This is because the arguments are being concatenated together, so you end up with the command:



      echo foo bar baz


       



      Since there is no way to get around the command being passed through a shell, you just have to ensure that what you passed can survive the shell evaluation. The way I usually accomplish this is with printf "%q "



      For example:



      cmd=(echo "foo bar" baz)
      ssh user@server "$(printf "%q " "$cmd[@]")"


      Which results in the output:



      foo bar baz


      While it's cleaner and easier to understand with cmd being a separate var, it's not required. The following works just the same:



      ssh user@server "$(printf "%q " echo "foo bar" baz)"


       



      This also works fine with your shell argument example:



      cmd=(sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3")
      ssh user@server "$(printf "%q " "$cmd[@]")"


      Which results in the output:



      1=<my arg1> 2=<my arg2> 3=<my arg3>


       




      As an alternative solution, you can pass your command as a complete shell script. For example:



      ssh user@server <<'EOF'
      sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3"
      EOF


      There are drawbacks to this solution though as it's harder to do programmatically (generating the doc to pass on STDIN). Also because you're using STDIN, if you want the script on the remote side to read STDIN, you can't (at least not without some trickery).






      share|improve this answer

























        up vote
        3
        down vote



        accepted







        up vote
        3
        down vote



        accepted






        The first thing to understand in this process is how ssh handles its arguments. I don't mean the arguments to the thing you're trying to run, but arguments of ssh. When you invoke ssh, the arguments after the remote host specification (user@server) are concatenated together, and passed through the shell on the remote end. This is important to note, as just because your arguments are properly split on the local side, does not mean they will be properly split on the remote side.



        To use your example:



        ssh user@server 'echo "hello $1"' sh "world"


        These arguments get concatenated as the command:



        echo "hello $1" sh world


        This is why you get



        hello sh world


        The double space between hello and sh is because that's where $1 was supposed to go, but there is no $1.



         



        As another example, without the $1, is:



        ssh user@server echo "foo bar" baz


        Which results in the output:



        foo bar baz


        This is because the arguments are being concatenated together, so you end up with the command:



        echo foo bar baz


         



        Since there is no way to get around the command being passed through a shell, you just have to ensure that what you passed can survive the shell evaluation. The way I usually accomplish this is with printf "%q "



        For example:



        cmd=(echo "foo bar" baz)
        ssh user@server "$(printf "%q " "$cmd[@]")"


        Which results in the output:



        foo bar baz


        While it's cleaner and easier to understand with cmd being a separate var, it's not required. The following works just the same:



        ssh user@server "$(printf "%q " echo "foo bar" baz)"


         



        This also works fine with your shell argument example:



        cmd=(sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3")
        ssh user@server "$(printf "%q " "$cmd[@]")"


        Which results in the output:



        1=<my arg1> 2=<my arg2> 3=<my arg3>


         




        As an alternative solution, you can pass your command as a complete shell script. For example:



        ssh user@server <<'EOF'
        sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3"
        EOF


        There are drawbacks to this solution though as it's harder to do programmatically (generating the doc to pass on STDIN). Also because you're using STDIN, if you want the script on the remote side to read STDIN, you can't (at least not without some trickery).






        share|improve this answer















        The first thing to understand in this process is how ssh handles its arguments. I don't mean the arguments to the thing you're trying to run, but arguments of ssh. When you invoke ssh, the arguments after the remote host specification (user@server) are concatenated together, and passed through the shell on the remote end. This is important to note, as just because your arguments are properly split on the local side, does not mean they will be properly split on the remote side.



        To use your example:



        ssh user@server 'echo "hello $1"' sh "world"


        These arguments get concatenated as the command:



        echo "hello $1" sh world


        This is why you get



        hello sh world


        The double space between hello and sh is because that's where $1 was supposed to go, but there is no $1.



         



        As another example, without the $1, is:



        ssh user@server echo "foo bar" baz


        Which results in the output:



        foo bar baz


        This is because the arguments are being concatenated together, so you end up with the command:



        echo foo bar baz


         



        Since there is no way to get around the command being passed through a shell, you just have to ensure that what you passed can survive the shell evaluation. The way I usually accomplish this is with printf "%q "



        For example:



        cmd=(echo "foo bar" baz)
        ssh user@server "$(printf "%q " "$cmd[@]")"


        Which results in the output:



        foo bar baz


        While it's cleaner and easier to understand with cmd being a separate var, it's not required. The following works just the same:



        ssh user@server "$(printf "%q " echo "foo bar" baz)"


         



        This also works fine with your shell argument example:



        cmd=(sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3")
        ssh user@server "$(printf "%q " "$cmd[@]")"


        Which results in the output:



        1=<my arg1> 2=<my arg2> 3=<my arg3>


         




        As an alternative solution, you can pass your command as a complete shell script. For example:



        ssh user@server <<'EOF'
        sh -c 'echo 1="<$1>" 2="<$2>" 3="<$3>"' sh "my arg1" "my arg2" "my arg3"
        EOF


        There are drawbacks to this solution though as it's harder to do programmatically (generating the doc to pass on STDIN). Also because you're using STDIN, if you want the script on the remote side to read STDIN, you can't (at least not without some trickery).







        share|improve this answer















        share|improve this answer



        share|improve this answer








        edited Jun 15 at 16:23


























        answered Jun 15 at 15:55









        Patrick

        47.5k10125176




        47.5k10125176






















             

            draft saved


            draft discarded


























             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f450020%2fexecuting-sh-c-script-through-ssh-passing-arguments-safely-and-sanely%23new-answer', 'question_page');

            );

            Post as a guest













































































            Popular posts from this blog

            Peggy Mitchell

            Palaiologos

            The Forum (Inglewood, California)