Differences between revisions 4 and 5
Revision 4 as of 2007-11-30 03:05:06
Size: 1709
Editor: GreyCat
Comment: change internal links
Revision 5 as of 2008-05-09 16:43:50
Size: 3205
Editor: GreyCat
Comment: people who PISS me the FUCK off need to read this and then SHUT UP.
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
== I'm trying to construct a command dynamically, but I can't figure out how to deal with quoted multi-word arguments. == == I'm trying to put a command in a variable, but the complex cases always fail! ==
Line 11: Line 11:
This fails because of word-splitting. When {{{$args}}} is evaluated, it becomes four words: {{{'The}}} is the second word, and {{{subject'}}} is the third word. This fails because of word-splitting. When {{{$args}}} is expanded, it becomes four words. {{{'The}}} is the second word, and {{{subject'}}} is the third word.
Line 13: Line 13:
What's needed is a way to maintain each word as a separate item, even if that word contains multiple spaces. Quotes won't do it, but an [:BashFAQ#faq5:array] will. What's needed is a way to maintain each argument as a separate word, even if that argument contains spaces. Quotes won't do it, but an [:BashFAQ/005:array] will:
Line 21: Line 21:
Often, this question arises when someone is trying to use {{{dialog}}} to construct a menu on the fly. For an example of how to do this properly, see [:BashFAQ#faq40:FAQ #40]. Often, this question arises when someone is trying to use {{{dialog}}} to construct a menu on the fly. The {{{dialog}}} command can't be hard-coded, because its parameters are supplied based on data only available at run time. For an example of how to do this properly, see [:BashFAQ/040:FAQ #40].
Line 23: Line 23:
Another reason people attempt to do this is because they want to {{{echo "I am going to run this command: $command"}}} before they actually run it. If that's all you want, then simply use the {{{set -x}}} command, or invoke your script with {{{#!/bin/bash -x}}} or {{{bash -x ./myscript}}}. Note that you can turn it off and back on inside the script with {{{set +x}}} and {{{set -x}}}. Another reason people attempt to do this is because they want their script to print each command before it runs it. If that's all you want, then simply use the {{{set -x}}} command, or invoke your script with {{{#!/bin/bash -x}}} or {{{bash -x ./myscript}}}. Note that you can turn it off and back on inside the script with {{{set +x}}} and {{{set -x}}}.
Line 25: Line 25:
It's worth noting that you ''cannot'' put a pipeline command into an array variable and then execute it using the {{{"${array[@]}"}}} technique. The only way to store a pipeline in a variable would be to add (carefully!) a layer of quotes if necessary, store it in a string variable, and then use {{{eval}}} or {{{sh}}} to run the variable. This is [:BashFAQ#faq48:not recommended], for security reasons. It's worth noting that you ''cannot'' put a pipeline command into an array variable and then execute it using the {{{"${array[@]}"}}} technique. The only way to store a pipeline in a variable would be to add (carefully!) a layer of quotes if necessary, store it in a string variable, and then use {{{eval}}} or {{{sh}}} to run the variable. This is [:BashFAQ/048:not recommended], for security reasons. The same thing applies to commands involving redirection, {{{if}}} or {{{while}}} statements, and so on.

Some people get into trouble because they want to have their script print their commands ''including redirections'' before it runs them. {{{set -x}}} shows the command without redirections. People try to work around this by doing things like:
{{{
    # Non-working example
    command="mysql -u me -p somedbname < file"
    ((DEBUG)) && echo "$command"
    "$command"
}}}
(This is so common that I include it explicitly, even though it's repeating what I already wrote.)

Once again, ''this does not work''. Not even using an array works here. The only thing that would work is rigorously escaping the command to be sure ''no'' metacharacters will cause serious security problems, and then using `eval` or `sh` to re-read the command. '''Please don't do that!'''

If your head is SO far up your ass that you still think you need to write out every command you're about to run before you run it, AND that you must include all redirections, then just do this:
{{{
    # Working example
    echo "mysql -u me -p somedbname < file"
    mysql -u me -p somedbname < file
}}}
Don't use a variable at all. Just copy and paste the command, wrap an extra layer of quotes around it (sometimes tricky), and stick an `echo` in front of it.

My personal recommendation would be just to use {{{set -x}}} and not worry about it.

Anchor(faq50)

I'm trying to put a command in a variable, but the complex cases always fail!

Some people attempt to do things like this:

    # Non-working example
    args="-s 'The subject' $address"
    mail $args < $body

This fails because of word-splitting. When $args is expanded, it becomes four words. 'The is the second word, and subject' is the third word.

What's needed is a way to maintain each argument as a separate word, even if that argument contains spaces. Quotes won't do it, but an [:BashFAQ/005:array] will:

    # Working example
    args=(-s "The subject" "$address")
    mail "${args[@]}" < $body

Often, this question arises when someone is trying to use dialog to construct a menu on the fly. The dialog command can't be hard-coded, because its parameters are supplied based on data only available at run time. For an example of how to do this properly, see [:BashFAQ/040:FAQ #40].

Another reason people attempt to do this is because they want their script to print each command before it runs it. If that's all you want, then simply use the set -x command, or invoke your script with #!/bin/bash -x or bash -x ./myscript. Note that you can turn it off and back on inside the script with set +x and set -x.

It's worth noting that you cannot put a pipeline command into an array variable and then execute it using the "${array[@]}" technique. The only way to store a pipeline in a variable would be to add (carefully!) a layer of quotes if necessary, store it in a string variable, and then use eval or sh to run the variable. This is [:BashFAQ/048:not recommended], for security reasons. The same thing applies to commands involving redirection, if or while statements, and so on.

Some people get into trouble because they want to have their script print their commands including redirections before it runs them. set -x shows the command without redirections. People try to work around this by doing things like:

    # Non-working example
    command="mysql -u me -p somedbname < file"
    ((DEBUG)) && echo "$command"
    "$command"

(This is so common that I include it explicitly, even though it's repeating what I already wrote.)

Once again, this does not work. Not even using an array works here. The only thing that would work is rigorously escaping the command to be sure no metacharacters will cause serious security problems, and then using eval or sh to re-read the command. Please don't do that!

If your head is SO far up your ass that you still think you need to write out every command you're about to run before you run it, AND that you must include all redirections, then just do this:

    # Working example
    echo "mysql -u me -p somedbname < file"
    mysql -u me -p somedbname < file

Don't use a variable at all. Just copy and paste the command, wrap an extra layer of quotes around it (sometimes tricky), and stick an echo in front of it.

My personal recommendation would be just to use set -x and not worry about it.

BashFAQ/050 (last edited 2022-10-04 13:22:06 by emanuele6)