1

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?

mklement0
  • 382,024
  • 64
  • 607
  • 775
Marta Koprivnik
  • 385
  • 2
  • 3
  • 10

2 Answers2

3

What you are doing wrong is trying to store a command in a variable. For simplicity, robustness, etc. commands are stored in aliases (if no arguments) or functions (if arguments), not variables. In this case:

$ alias foo='grep X'
$ echo "aXb" | foo
aXb

I recommend you read the book Shell Scripting Recipes by Chris Johnson ASAP to get the basics of shell programming and then Effective Awk Programming, 4th Edition, by Arnold Robbins when you're ready to start writing scripts to manipulate text.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • 1
    While using an _alias_ is an option, the fact that alias expansion is by default _disabled_ in scripts makes this solution awkward: you'd have to run `shopt -o expand_aliases` in your script first. – mklement0 Apr 23 '16 at 17:42
  • @mklement0 what makes you think the OP wants to put this in a script? – Ed Morton Apr 23 '16 at 20:59
  • While you can certainly define variables and even functions in an interactive shell, they're _typically_ used in a script. Whatever the OP's specific use case may be, that aspect wasn't specified, so it's worth pointing out that in scripts aliases won't get expanded by default – mklement0 Apr 23 '16 at 21:02
  • My aliases are defined in .env or .profile files and I just use them in my interactive shell. I don't use them in any scripts and I have no need to define them in a script. YMMV I suppose. – Ed Morton Apr 23 '16 at 21:04
  • 1
    To summarize: ++ for your alias approach to cover use in _interactive_ shells; between your answer and mine we now have both use cases covered (interactive use vs. use in a script). I'd suggest showing the alias definition with the OP's exact command, however, to highlight potential quoting troubles. – mklement0 Apr 23 '16 at 21:11
3

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[@]}".

mklement0
  • 382,024
  • 64
  • 607
  • 775