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.