The reason your approach didn't work is that shell metacharacters such as | lose their syntactical meaning when they are embedded in a string. The answers to this related question explain why; that question is about embedded quotes, but the answers still apply analogously.
Also, it is generally ill-advised to store commands in variables; in short:
- Without the - ill-advised - use of
eval, it only works with simple commands (commands that aren't composed of multiple commands with the help of control operators such as | and &&),
- and then only robustly if you store the command's arguments in an array.
To get exactly what you want, there is no way around the use of eval, which is generally discouraged for security reasons.
- In short: Only use
eval if you fully control or trust the string that you pass to eval; that is, use of eval with, say, variable $var (eval "$var") is acceptable, if and only if:
- you fully control the value assigned to
$var, know that it is a command, and know that it works as intended (if even part of the value is provided by an outside source, the next point applies)
- or you trust the source of any outside contribution to the value of
$var (whether it forms the value of $var in full or in part) - specifically, you need to be sure that no attempt will be made to maliciously provide/inject unwanted commands.
The most predictable eval-based solution is as follows, which improves on AxT_8041's answer (works with both bash and ksh):
Note: Since this command is constructed by you, as a literal, you fully control the command string, so eval is acceptable in this case.
cmdLine='ls -ltr|wc -l'
printf "%s\n" "$cmdLine"; eval "$cmdLine"
Quoting the command gets tricky with more complex command lines, in which case you can use a here-document:
# Read the command into variable $cmdLine.
read -d '' -r cmdLine <<'EOF'
cat passwd.txt|tr ' ' '\n'|egrep -i "$pwd_in"|sort
EOF
printf "%s\n" "$cmdLine"; eval "$cmdLine"
Note: While this command is also constructed by you, it is formed by incorporating the value of variable $pwd_in, so in order to use eval safely here, you must know or trust that $pwd_in has the expected kind of value, notably that it doesn't contain attempts to inject commands.
A safe, but limited solution is to use a diagnostic feature offered by the shell:
In the case of bash, set -v automatically echoes each raw command line before it is executed (no need to define the command of interest in a variable first):
set -v # Turn echoing of raw command lines on.
ls -ltr | wc -l # Execute the command of interest.
set +v # Turn back off.
This yields, for example:
ls -ltr | wc -l
15
set +v
However, there are limitations:
The raw command lines that Bash echoes are sent to stderr.
- Note that
set -x in ceving's answer is a related feature, but it works differently: It will individually echo the simple commands (ls -ltr and wc -l) that make up the pipeline in their already expanded form.
The set +v output line cannot be suppressed, from what I can tell.
You could try to work around these issues, but it won't be easy or pretty.