Search code examples
bashvariablesgreppipeline

How do I store a command in a variable and use it in a pipeline?


If i use this command in pipeline, it's working very well;

pipeline ... | grep -P '^[^\s]*\s3\s'

But if I want to set grep into variable like:

var="grep -P '^[^\s]*\s3\s'"

And if I put variable in pipeline;

pipeline ... | $var

nothing happens, like there isn't any matches.

Any help what am I doing wrong?


Solution

  • The robust way to store a simple command in a variable in Bash is to use an array:

    # Store the command names and arguments individually
    # in the elements of an *array*.
    cmd=( grep -P '^[^\s]*\s3\s' )
    
    # Use the entire array as the command to execute - be sure to
    # double-quote ${cmd[@]}.
    echo 'before 3 after' | "${cmd[@]}"
    

    If, by contrast, your command is more than a simple command and, for instance, involves pipes, multiple commands, loops, ..., defining a function is the right approach:

    # Define a function encapsulating the command...
    myGrep() { grep -P '^[^\s]*\s3\s'; }
    
    # ... and use it:
    echo 'before 3 after' | myGrep
    

    Why what you tried didn't work:

    var="grep -P '^[^\s]*\s3\s'"
    

    causes the single quotes around the regex to become a literal, embedded part of $var's value.

    When you then use $var - unquoted - as a command, the following happens:

    • Bash performs word-splitting, which means that it breaks the value of $var into words (separate tokens) by whitespace (the chars. defined in special variable $IFS, which contains a space, a tab, and a newline character by default).

      • Bash also performs globbing (pathname expansion) on the resulting works, which is not a problem here, but can have unintended consequences in general.
      • Also, if any of your original arguments had embedded whitespace, word splitting would split them into multiple words, and your original argument partitioning is lost.
        • (As an aside: "$var" - i.e., double-quoting the variable reference - is not a solution, because then the entire string is treated as the command name.)
    • Specifically, the resulting words are:

      • grep
      • -P
      • '^[^\s]*\s3\s' - including the surrounding single quotes
    • The words are then interpreted as the name of the command and its arguments, and invoked as such.

      • Given that the pattern argument passed to grep starts with a literal single quote, matching won't work as intended.

    Short of using eval "$var" - which is NOT recommended for security reasons - you cannot persuade Bash to see the embedded single quotes as syntactical elements that should be removed (a process appropriate called quote removal).

    Using an array bypasses all these problems by storing arguments in individual elements and letting Bash robustly assemble them into a command with "${cmd[@]}".