How can we run a command stored in a variable?











up vote
18
down vote

favorite
9












$ ls -l /tmp/test/my dir/
total 0


I was wondering why the following ways to run the above command fail or succeed? Thanks.



$ abc='ls -l "/tmp/test/my dir"'

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

$ bash -c $abc
'my dir'

$ bash -c "$abc"
total 0

$ eval $abc
total 0

$ eval "$abc"
total 0









share|improve this question




















  • 2




    mywiki.wooledge.org/BashFAQ/050
    – Kamaraj
    May 20 at 12:49










  • Security implications of forgetting to quote a variable in bash/POSIX shells  — But what if …?
    – Scott
    Jul 30 at 23:20















up vote
18
down vote

favorite
9












$ ls -l /tmp/test/my dir/
total 0


I was wondering why the following ways to run the above command fail or succeed? Thanks.



$ abc='ls -l "/tmp/test/my dir"'

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

$ bash -c $abc
'my dir'

$ bash -c "$abc"
total 0

$ eval $abc
total 0

$ eval "$abc"
total 0









share|improve this question




















  • 2




    mywiki.wooledge.org/BashFAQ/050
    – Kamaraj
    May 20 at 12:49










  • Security implications of forgetting to quote a variable in bash/POSIX shells  — But what if …?
    – Scott
    Jul 30 at 23:20













up vote
18
down vote

favorite
9









up vote
18
down vote

favorite
9






9





$ ls -l /tmp/test/my dir/
total 0


I was wondering why the following ways to run the above command fail or succeed? Thanks.



$ abc='ls -l "/tmp/test/my dir"'

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

$ bash -c $abc
'my dir'

$ bash -c "$abc"
total 0

$ eval $abc
total 0

$ eval "$abc"
total 0









share|improve this question















$ ls -l /tmp/test/my dir/
total 0


I was wondering why the following ways to run the above command fail or succeed? Thanks.



$ abc='ls -l "/tmp/test/my dir"'

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

$ bash -c $abc
'my dir'

$ bash -c "$abc"
total 0

$ eval $abc
total 0

$ eval "$abc"
total 0






bash shell quoting variable






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited May 20 at 16:08









Gilles

520k12510381569




520k12510381569










asked May 20 at 12:46









Tim

24.9k70239434




24.9k70239434








  • 2




    mywiki.wooledge.org/BashFAQ/050
    – Kamaraj
    May 20 at 12:49










  • Security implications of forgetting to quote a variable in bash/POSIX shells  — But what if …?
    – Scott
    Jul 30 at 23:20














  • 2




    mywiki.wooledge.org/BashFAQ/050
    – Kamaraj
    May 20 at 12:49










  • Security implications of forgetting to quote a variable in bash/POSIX shells  — But what if …?
    – Scott
    Jul 30 at 23:20








2




2




mywiki.wooledge.org/BashFAQ/050
– Kamaraj
May 20 at 12:49




mywiki.wooledge.org/BashFAQ/050
– Kamaraj
May 20 at 12:49












Security implications of forgetting to quote a variable in bash/POSIX shells  — But what if …?
– Scott
Jul 30 at 23:20




Security implications of forgetting to quote a variable in bash/POSIX shells  — But what if …?
– Scott
Jul 30 at 23:20










3 Answers
3






active

oldest

votes

















up vote
28
down vote



accepted










This has been discussed in a number of questions on unix.SE, I'll try to collect all issues I can come up with here. References at the end.





Why it fails



The reason you face those problems is word splitting and the fact that quotes expanded from variables don't take effect.



The cases presented in the question:



$ abc='ls -l "/tmp/test/my dir"'


Here, $abc is split, and ls gets the two arguments "/tmp/test/my and dir" (with the quotes present):



$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory


Here, the expansion is quoted, so it's kept as a single word. The shell tries to find a program called ls -l "/tmp/test/my dir", spaces and quotes included.



$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory


And here, only the first word or $abc is taken as the argument to -c, so Bash just runs ls in the current directory. The other words are arguments to bash, and are used to fill $0, $1, etc.



$ bash -c $abc
'my dir'


With bash -c "$abc", and eval "$abc", there's an additional shell processing step, which does make the quotes work, but also causes all shell expansions to be processed again, so there's a risk of accidentally running a command expansion from user-provided data, unless you're very careful about quoting.





Better ways to do it



The two better ways to store a command are a) use a function instead, b) use an array variable.



Using a function:



Simply declare a function with the command inside, and run the function as if it were a command. Expansions in commands within the function are only processed when the command runs, not when it's defined, and you don't need to quote the individual commands.



# define it
myls() {
ls -l "/tmp/test/my dir"
}

# run it
myls


Using an array:



Arrays allow creating multi-word variables where the individual words contain white space. Here, the individual words are stored as distinct array elements, and the "${array[@]}" expansion expands each element as separate shell words:



# define the array
mycmd=(ls -l "/tmp/test/my dir")

# run the command
"${mycmd[@]}"


The syntax is slightly horrible, but arrays also allow you to build the command line piece-by-piece. For example:



mycmd=(ls)               # initial command
if [ "$want_detail" = 1 ]; then
mycmd+=(-l) # optional flag
fi
mycmd+=("$targetdir") # the filename

"${mycmd[@]}"


or keep parts of the command line constant and use the array fill just a part of it, options or filenames:



options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir

transmutate "${options[@]}" "${files[@]}" "$target"


The downside of arrays is that they're not a standard feature, so plain POSIX shells (like dash, the default /bin/sh in Debian/Ubuntu) don't support them. Bash, ksh and zsh do, however.





Be careful with eval!



As eval introduces an additional level of quote and expansion processing, you need to be careful with user input.
For example, this works as long as the user doesn't type in any single quotes:



read -r filename
cmd="ls -l '$filename'"
eval "$cmd";


But if they give the input '$(uname)'.txt, your script happily runs the command substitution.



A version with arrays is immune to that since the words are kept separate for the whole time, there's no quote or other processing for the contents of filename.



read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"




References





  • Word Splitting in BashGuide

  • BashFAQ/050 or "I'm trying to put a command in a variable, but the complex cases always fail!"

  • The question Why does my shell script choke on whitespace or other special characters?, which discusses a number of issues related to quoting and whitespace, including storing commands.






share|improve this answer



















  • 2




    you can get around the eval quoting thing by doing cmd="ls -l $(printf "%q" "$filename")". not pretty, but if the user is dead set on using an eval, it helps. It's also very useful for sending the command though similar things, such as ssh foohost "ls -l $(printf "%q" "$filename")", or in the sprit of this question: ssh foohost "$cmd".
    – Patrick
    May 20 at 19:39












  • Not directly related, but have you hard-coded the directory? In that case, you might want to look at alias. Something like: $ alias abc='ls -l "/tmp/test/my dir"'
    – Hopping Bunny
    May 23 at 2:25




















up vote
4
down vote













The safest way to run a (non-trivial) command is eval. Then you can write the command as you would do on the command line and it is executed exactly as if you had just entered it. But you have to quote everything.



Simple case:



abc='ls -l "/tmp/test/my dir"'
eval "$abc"


not so simple case:



# command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
abc='awk '''! a[$0]++ { print "foo: " $0; }''' inputfile'
eval "$abc"





share|improve this answer




























    up vote
    2
    down vote













    The second quote sign break the command.



    When I run:



    abc="ls -l '/home/wattana/Desktop'"
    $abc


    It gave me an error.



    But when I run



    abc="ls -l /home/wattana/Desktop"
    $abc


    There is no error at all



    There is no way to fix this at the time(for me) but you can avoid the error by not having space in directory name.



    This answer said the eval command can be used to fix this but it doesn't work for me
    :(






    share|improve this answer



















    • 1




      Yeah, that works as long as there's no need for e.g. filenames with embedded spaces (or ones containing glob characters).
      – ilkkachu
      May 20 at 13:22











    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%2f444946%2fhow-can-we-run-a-command-stored-in-a-variable%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    3 Answers
    3






    active

    oldest

    votes








    3 Answers
    3






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    28
    down vote



    accepted










    This has been discussed in a number of questions on unix.SE, I'll try to collect all issues I can come up with here. References at the end.





    Why it fails



    The reason you face those problems is word splitting and the fact that quotes expanded from variables don't take effect.



    The cases presented in the question:



    $ abc='ls -l "/tmp/test/my dir"'


    Here, $abc is split, and ls gets the two arguments "/tmp/test/my and dir" (with the quotes present):



    $ $abc
    ls: cannot access '"/tmp/test/my': No such file or directory
    ls: cannot access 'dir"': No such file or directory


    Here, the expansion is quoted, so it's kept as a single word. The shell tries to find a program called ls -l "/tmp/test/my dir", spaces and quotes included.



    $ "$abc"
    bash: ls -l "/tmp/test/my dir": No such file or directory


    And here, only the first word or $abc is taken as the argument to -c, so Bash just runs ls in the current directory. The other words are arguments to bash, and are used to fill $0, $1, etc.



    $ bash -c $abc
    'my dir'


    With bash -c "$abc", and eval "$abc", there's an additional shell processing step, which does make the quotes work, but also causes all shell expansions to be processed again, so there's a risk of accidentally running a command expansion from user-provided data, unless you're very careful about quoting.





    Better ways to do it



    The two better ways to store a command are a) use a function instead, b) use an array variable.



    Using a function:



    Simply declare a function with the command inside, and run the function as if it were a command. Expansions in commands within the function are only processed when the command runs, not when it's defined, and you don't need to quote the individual commands.



    # define it
    myls() {
    ls -l "/tmp/test/my dir"
    }

    # run it
    myls


    Using an array:



    Arrays allow creating multi-word variables where the individual words contain white space. Here, the individual words are stored as distinct array elements, and the "${array[@]}" expansion expands each element as separate shell words:



    # define the array
    mycmd=(ls -l "/tmp/test/my dir")

    # run the command
    "${mycmd[@]}"


    The syntax is slightly horrible, but arrays also allow you to build the command line piece-by-piece. For example:



    mycmd=(ls)               # initial command
    if [ "$want_detail" = 1 ]; then
    mycmd+=(-l) # optional flag
    fi
    mycmd+=("$targetdir") # the filename

    "${mycmd[@]}"


    or keep parts of the command line constant and use the array fill just a part of it, options or filenames:



    options=(-x -v)
    files=(file1 "file name with whitespace")
    target=/somedir

    transmutate "${options[@]}" "${files[@]}" "$target"


    The downside of arrays is that they're not a standard feature, so plain POSIX shells (like dash, the default /bin/sh in Debian/Ubuntu) don't support them. Bash, ksh and zsh do, however.





    Be careful with eval!



    As eval introduces an additional level of quote and expansion processing, you need to be careful with user input.
    For example, this works as long as the user doesn't type in any single quotes:



    read -r filename
    cmd="ls -l '$filename'"
    eval "$cmd";


    But if they give the input '$(uname)'.txt, your script happily runs the command substitution.



    A version with arrays is immune to that since the words are kept separate for the whole time, there's no quote or other processing for the contents of filename.



    read -r filename
    cmd=(ls -ld -- "$filename")
    "${cmd[@]}"




    References





    • Word Splitting in BashGuide

    • BashFAQ/050 or "I'm trying to put a command in a variable, but the complex cases always fail!"

    • The question Why does my shell script choke on whitespace or other special characters?, which discusses a number of issues related to quoting and whitespace, including storing commands.






    share|improve this answer



















    • 2




      you can get around the eval quoting thing by doing cmd="ls -l $(printf "%q" "$filename")". not pretty, but if the user is dead set on using an eval, it helps. It's also very useful for sending the command though similar things, such as ssh foohost "ls -l $(printf "%q" "$filename")", or in the sprit of this question: ssh foohost "$cmd".
      – Patrick
      May 20 at 19:39












    • Not directly related, but have you hard-coded the directory? In that case, you might want to look at alias. Something like: $ alias abc='ls -l "/tmp/test/my dir"'
      – Hopping Bunny
      May 23 at 2:25

















    up vote
    28
    down vote



    accepted










    This has been discussed in a number of questions on unix.SE, I'll try to collect all issues I can come up with here. References at the end.





    Why it fails



    The reason you face those problems is word splitting and the fact that quotes expanded from variables don't take effect.



    The cases presented in the question:



    $ abc='ls -l "/tmp/test/my dir"'


    Here, $abc is split, and ls gets the two arguments "/tmp/test/my and dir" (with the quotes present):



    $ $abc
    ls: cannot access '"/tmp/test/my': No such file or directory
    ls: cannot access 'dir"': No such file or directory


    Here, the expansion is quoted, so it's kept as a single word. The shell tries to find a program called ls -l "/tmp/test/my dir", spaces and quotes included.



    $ "$abc"
    bash: ls -l "/tmp/test/my dir": No such file or directory


    And here, only the first word or $abc is taken as the argument to -c, so Bash just runs ls in the current directory. The other words are arguments to bash, and are used to fill $0, $1, etc.



    $ bash -c $abc
    'my dir'


    With bash -c "$abc", and eval "$abc", there's an additional shell processing step, which does make the quotes work, but also causes all shell expansions to be processed again, so there's a risk of accidentally running a command expansion from user-provided data, unless you're very careful about quoting.





    Better ways to do it



    The two better ways to store a command are a) use a function instead, b) use an array variable.



    Using a function:



    Simply declare a function with the command inside, and run the function as if it were a command. Expansions in commands within the function are only processed when the command runs, not when it's defined, and you don't need to quote the individual commands.



    # define it
    myls() {
    ls -l "/tmp/test/my dir"
    }

    # run it
    myls


    Using an array:



    Arrays allow creating multi-word variables where the individual words contain white space. Here, the individual words are stored as distinct array elements, and the "${array[@]}" expansion expands each element as separate shell words:



    # define the array
    mycmd=(ls -l "/tmp/test/my dir")

    # run the command
    "${mycmd[@]}"


    The syntax is slightly horrible, but arrays also allow you to build the command line piece-by-piece. For example:



    mycmd=(ls)               # initial command
    if [ "$want_detail" = 1 ]; then
    mycmd+=(-l) # optional flag
    fi
    mycmd+=("$targetdir") # the filename

    "${mycmd[@]}"


    or keep parts of the command line constant and use the array fill just a part of it, options or filenames:



    options=(-x -v)
    files=(file1 "file name with whitespace")
    target=/somedir

    transmutate "${options[@]}" "${files[@]}" "$target"


    The downside of arrays is that they're not a standard feature, so plain POSIX shells (like dash, the default /bin/sh in Debian/Ubuntu) don't support them. Bash, ksh and zsh do, however.





    Be careful with eval!



    As eval introduces an additional level of quote and expansion processing, you need to be careful with user input.
    For example, this works as long as the user doesn't type in any single quotes:



    read -r filename
    cmd="ls -l '$filename'"
    eval "$cmd";


    But if they give the input '$(uname)'.txt, your script happily runs the command substitution.



    A version with arrays is immune to that since the words are kept separate for the whole time, there's no quote or other processing for the contents of filename.



    read -r filename
    cmd=(ls -ld -- "$filename")
    "${cmd[@]}"




    References





    • Word Splitting in BashGuide

    • BashFAQ/050 or "I'm trying to put a command in a variable, but the complex cases always fail!"

    • The question Why does my shell script choke on whitespace or other special characters?, which discusses a number of issues related to quoting and whitespace, including storing commands.






    share|improve this answer



















    • 2




      you can get around the eval quoting thing by doing cmd="ls -l $(printf "%q" "$filename")". not pretty, but if the user is dead set on using an eval, it helps. It's also very useful for sending the command though similar things, such as ssh foohost "ls -l $(printf "%q" "$filename")", or in the sprit of this question: ssh foohost "$cmd".
      – Patrick
      May 20 at 19:39












    • Not directly related, but have you hard-coded the directory? In that case, you might want to look at alias. Something like: $ alias abc='ls -l "/tmp/test/my dir"'
      – Hopping Bunny
      May 23 at 2:25















    up vote
    28
    down vote



    accepted







    up vote
    28
    down vote



    accepted






    This has been discussed in a number of questions on unix.SE, I'll try to collect all issues I can come up with here. References at the end.





    Why it fails



    The reason you face those problems is word splitting and the fact that quotes expanded from variables don't take effect.



    The cases presented in the question:



    $ abc='ls -l "/tmp/test/my dir"'


    Here, $abc is split, and ls gets the two arguments "/tmp/test/my and dir" (with the quotes present):



    $ $abc
    ls: cannot access '"/tmp/test/my': No such file or directory
    ls: cannot access 'dir"': No such file or directory


    Here, the expansion is quoted, so it's kept as a single word. The shell tries to find a program called ls -l "/tmp/test/my dir", spaces and quotes included.



    $ "$abc"
    bash: ls -l "/tmp/test/my dir": No such file or directory


    And here, only the first word or $abc is taken as the argument to -c, so Bash just runs ls in the current directory. The other words are arguments to bash, and are used to fill $0, $1, etc.



    $ bash -c $abc
    'my dir'


    With bash -c "$abc", and eval "$abc", there's an additional shell processing step, which does make the quotes work, but also causes all shell expansions to be processed again, so there's a risk of accidentally running a command expansion from user-provided data, unless you're very careful about quoting.





    Better ways to do it



    The two better ways to store a command are a) use a function instead, b) use an array variable.



    Using a function:



    Simply declare a function with the command inside, and run the function as if it were a command. Expansions in commands within the function are only processed when the command runs, not when it's defined, and you don't need to quote the individual commands.



    # define it
    myls() {
    ls -l "/tmp/test/my dir"
    }

    # run it
    myls


    Using an array:



    Arrays allow creating multi-word variables where the individual words contain white space. Here, the individual words are stored as distinct array elements, and the "${array[@]}" expansion expands each element as separate shell words:



    # define the array
    mycmd=(ls -l "/tmp/test/my dir")

    # run the command
    "${mycmd[@]}"


    The syntax is slightly horrible, but arrays also allow you to build the command line piece-by-piece. For example:



    mycmd=(ls)               # initial command
    if [ "$want_detail" = 1 ]; then
    mycmd+=(-l) # optional flag
    fi
    mycmd+=("$targetdir") # the filename

    "${mycmd[@]}"


    or keep parts of the command line constant and use the array fill just a part of it, options or filenames:



    options=(-x -v)
    files=(file1 "file name with whitespace")
    target=/somedir

    transmutate "${options[@]}" "${files[@]}" "$target"


    The downside of arrays is that they're not a standard feature, so plain POSIX shells (like dash, the default /bin/sh in Debian/Ubuntu) don't support them. Bash, ksh and zsh do, however.





    Be careful with eval!



    As eval introduces an additional level of quote and expansion processing, you need to be careful with user input.
    For example, this works as long as the user doesn't type in any single quotes:



    read -r filename
    cmd="ls -l '$filename'"
    eval "$cmd";


    But if they give the input '$(uname)'.txt, your script happily runs the command substitution.



    A version with arrays is immune to that since the words are kept separate for the whole time, there's no quote or other processing for the contents of filename.



    read -r filename
    cmd=(ls -ld -- "$filename")
    "${cmd[@]}"




    References





    • Word Splitting in BashGuide

    • BashFAQ/050 or "I'm trying to put a command in a variable, but the complex cases always fail!"

    • The question Why does my shell script choke on whitespace or other special characters?, which discusses a number of issues related to quoting and whitespace, including storing commands.






    share|improve this answer














    This has been discussed in a number of questions on unix.SE, I'll try to collect all issues I can come up with here. References at the end.





    Why it fails



    The reason you face those problems is word splitting and the fact that quotes expanded from variables don't take effect.



    The cases presented in the question:



    $ abc='ls -l "/tmp/test/my dir"'


    Here, $abc is split, and ls gets the two arguments "/tmp/test/my and dir" (with the quotes present):



    $ $abc
    ls: cannot access '"/tmp/test/my': No such file or directory
    ls: cannot access 'dir"': No such file or directory


    Here, the expansion is quoted, so it's kept as a single word. The shell tries to find a program called ls -l "/tmp/test/my dir", spaces and quotes included.



    $ "$abc"
    bash: ls -l "/tmp/test/my dir": No such file or directory


    And here, only the first word or $abc is taken as the argument to -c, so Bash just runs ls in the current directory. The other words are arguments to bash, and are used to fill $0, $1, etc.



    $ bash -c $abc
    'my dir'


    With bash -c "$abc", and eval "$abc", there's an additional shell processing step, which does make the quotes work, but also causes all shell expansions to be processed again, so there's a risk of accidentally running a command expansion from user-provided data, unless you're very careful about quoting.





    Better ways to do it



    The two better ways to store a command are a) use a function instead, b) use an array variable.



    Using a function:



    Simply declare a function with the command inside, and run the function as if it were a command. Expansions in commands within the function are only processed when the command runs, not when it's defined, and you don't need to quote the individual commands.



    # define it
    myls() {
    ls -l "/tmp/test/my dir"
    }

    # run it
    myls


    Using an array:



    Arrays allow creating multi-word variables where the individual words contain white space. Here, the individual words are stored as distinct array elements, and the "${array[@]}" expansion expands each element as separate shell words:



    # define the array
    mycmd=(ls -l "/tmp/test/my dir")

    # run the command
    "${mycmd[@]}"


    The syntax is slightly horrible, but arrays also allow you to build the command line piece-by-piece. For example:



    mycmd=(ls)               # initial command
    if [ "$want_detail" = 1 ]; then
    mycmd+=(-l) # optional flag
    fi
    mycmd+=("$targetdir") # the filename

    "${mycmd[@]}"


    or keep parts of the command line constant and use the array fill just a part of it, options or filenames:



    options=(-x -v)
    files=(file1 "file name with whitespace")
    target=/somedir

    transmutate "${options[@]}" "${files[@]}" "$target"


    The downside of arrays is that they're not a standard feature, so plain POSIX shells (like dash, the default /bin/sh in Debian/Ubuntu) don't support them. Bash, ksh and zsh do, however.





    Be careful with eval!



    As eval introduces an additional level of quote and expansion processing, you need to be careful with user input.
    For example, this works as long as the user doesn't type in any single quotes:



    read -r filename
    cmd="ls -l '$filename'"
    eval "$cmd";


    But if they give the input '$(uname)'.txt, your script happily runs the command substitution.



    A version with arrays is immune to that since the words are kept separate for the whole time, there's no quote or other processing for the contents of filename.



    read -r filename
    cmd=(ls -ld -- "$filename")
    "${cmd[@]}"




    References





    • Word Splitting in BashGuide

    • BashFAQ/050 or "I'm trying to put a command in a variable, but the complex cases always fail!"

    • The question Why does my shell script choke on whitespace or other special characters?, which discusses a number of issues related to quoting and whitespace, including storing commands.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 14 at 10:11









    Stéphane Chazelas

    293k54548890




    293k54548890










    answered May 20 at 12:58









    ilkkachu

    53.6k781146




    53.6k781146








    • 2




      you can get around the eval quoting thing by doing cmd="ls -l $(printf "%q" "$filename")". not pretty, but if the user is dead set on using an eval, it helps. It's also very useful for sending the command though similar things, such as ssh foohost "ls -l $(printf "%q" "$filename")", or in the sprit of this question: ssh foohost "$cmd".
      – Patrick
      May 20 at 19:39












    • Not directly related, but have you hard-coded the directory? In that case, you might want to look at alias. Something like: $ alias abc='ls -l "/tmp/test/my dir"'
      – Hopping Bunny
      May 23 at 2:25
















    • 2




      you can get around the eval quoting thing by doing cmd="ls -l $(printf "%q" "$filename")". not pretty, but if the user is dead set on using an eval, it helps. It's also very useful for sending the command though similar things, such as ssh foohost "ls -l $(printf "%q" "$filename")", or in the sprit of this question: ssh foohost "$cmd".
      – Patrick
      May 20 at 19:39












    • Not directly related, but have you hard-coded the directory? In that case, you might want to look at alias. Something like: $ alias abc='ls -l "/tmp/test/my dir"'
      – Hopping Bunny
      May 23 at 2:25










    2




    2




    you can get around the eval quoting thing by doing cmd="ls -l $(printf "%q" "$filename")". not pretty, but if the user is dead set on using an eval, it helps. It's also very useful for sending the command though similar things, such as ssh foohost "ls -l $(printf "%q" "$filename")", or in the sprit of this question: ssh foohost "$cmd".
    – Patrick
    May 20 at 19:39






    you can get around the eval quoting thing by doing cmd="ls -l $(printf "%q" "$filename")". not pretty, but if the user is dead set on using an eval, it helps. It's also very useful for sending the command though similar things, such as ssh foohost "ls -l $(printf "%q" "$filename")", or in the sprit of this question: ssh foohost "$cmd".
    – Patrick
    May 20 at 19:39














    Not directly related, but have you hard-coded the directory? In that case, you might want to look at alias. Something like: $ alias abc='ls -l "/tmp/test/my dir"'
    – Hopping Bunny
    May 23 at 2:25






    Not directly related, but have you hard-coded the directory? In that case, you might want to look at alias. Something like: $ alias abc='ls -l "/tmp/test/my dir"'
    – Hopping Bunny
    May 23 at 2:25














    up vote
    4
    down vote













    The safest way to run a (non-trivial) command is eval. Then you can write the command as you would do on the command line and it is executed exactly as if you had just entered it. But you have to quote everything.



    Simple case:



    abc='ls -l "/tmp/test/my dir"'
    eval "$abc"


    not so simple case:



    # command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
    abc='awk '''! a[$0]++ { print "foo: " $0; }''' inputfile'
    eval "$abc"





    share|improve this answer

























      up vote
      4
      down vote













      The safest way to run a (non-trivial) command is eval. Then you can write the command as you would do on the command line and it is executed exactly as if you had just entered it. But you have to quote everything.



      Simple case:



      abc='ls -l "/tmp/test/my dir"'
      eval "$abc"


      not so simple case:



      # command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
      abc='awk '''! a[$0]++ { print "foo: " $0; }''' inputfile'
      eval "$abc"





      share|improve this answer























        up vote
        4
        down vote










        up vote
        4
        down vote









        The safest way to run a (non-trivial) command is eval. Then you can write the command as you would do on the command line and it is executed exactly as if you had just entered it. But you have to quote everything.



        Simple case:



        abc='ls -l "/tmp/test/my dir"'
        eval "$abc"


        not so simple case:



        # command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
        abc='awk '''! a[$0]++ { print "foo: " $0; }''' inputfile'
        eval "$abc"





        share|improve this answer












        The safest way to run a (non-trivial) command is eval. Then you can write the command as you would do on the command line and it is executed exactly as if you had just entered it. But you have to quote everything.



        Simple case:



        abc='ls -l "/tmp/test/my dir"'
        eval "$abc"


        not so simple case:



        # command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
        abc='awk '''! a[$0]++ { print "foo: " $0; }''' inputfile'
        eval "$abc"






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered May 20 at 13:20









        Hauke Laging

        54.9k1283130




        54.9k1283130






















            up vote
            2
            down vote













            The second quote sign break the command.



            When I run:



            abc="ls -l '/home/wattana/Desktop'"
            $abc


            It gave me an error.



            But when I run



            abc="ls -l /home/wattana/Desktop"
            $abc


            There is no error at all



            There is no way to fix this at the time(for me) but you can avoid the error by not having space in directory name.



            This answer said the eval command can be used to fix this but it doesn't work for me
            :(






            share|improve this answer



















            • 1




              Yeah, that works as long as there's no need for e.g. filenames with embedded spaces (or ones containing glob characters).
              – ilkkachu
              May 20 at 13:22















            up vote
            2
            down vote













            The second quote sign break the command.



            When I run:



            abc="ls -l '/home/wattana/Desktop'"
            $abc


            It gave me an error.



            But when I run



            abc="ls -l /home/wattana/Desktop"
            $abc


            There is no error at all



            There is no way to fix this at the time(for me) but you can avoid the error by not having space in directory name.



            This answer said the eval command can be used to fix this but it doesn't work for me
            :(






            share|improve this answer



















            • 1




              Yeah, that works as long as there's no need for e.g. filenames with embedded spaces (or ones containing glob characters).
              – ilkkachu
              May 20 at 13:22













            up vote
            2
            down vote










            up vote
            2
            down vote









            The second quote sign break the command.



            When I run:



            abc="ls -l '/home/wattana/Desktop'"
            $abc


            It gave me an error.



            But when I run



            abc="ls -l /home/wattana/Desktop"
            $abc


            There is no error at all



            There is no way to fix this at the time(for me) but you can avoid the error by not having space in directory name.



            This answer said the eval command can be used to fix this but it doesn't work for me
            :(






            share|improve this answer














            The second quote sign break the command.



            When I run:



            abc="ls -l '/home/wattana/Desktop'"
            $abc


            It gave me an error.



            But when I run



            abc="ls -l /home/wattana/Desktop"
            $abc


            There is no error at all



            There is no way to fix this at the time(for me) but you can avoid the error by not having space in directory name.



            This answer said the eval command can be used to fix this but it doesn't work for me
            :(







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited May 20 at 16:26









            Saad

            1034




            1034










            answered May 20 at 13:02









            Wattana Gaming

            291




            291








            • 1




              Yeah, that works as long as there's no need for e.g. filenames with embedded spaces (or ones containing glob characters).
              – ilkkachu
              May 20 at 13:22














            • 1




              Yeah, that works as long as there's no need for e.g. filenames with embedded spaces (or ones containing glob characters).
              – ilkkachu
              May 20 at 13:22








            1




            1




            Yeah, that works as long as there's no need for e.g. filenames with embedded spaces (or ones containing glob characters).
            – ilkkachu
            May 20 at 13:22




            Yeah, that works as long as there's no need for e.g. filenames with embedded spaces (or ones containing glob characters).
            – ilkkachu
            May 20 at 13:22


















             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f444946%2fhow-can-we-run-a-command-stored-in-a-variable%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

            Morgemoulin

            Scott Moir

            Souastre