3

I need to execute command and I need to display which command I have executed. So I have been trying like storing commands in an variable and executing that variable. While showing output I am printing variable value as below.

var="ls -ltr";
echo "$var \n `$var`";

above one is giving output as i wanted, but when i am using commands which are mentioned below, it is throwing me an error.can someone suggest me how to store below mentioned commands.

var="ls -ltr|wc -l";
echo "$var \n `$var`";

Output error:

ls -ltr | wc -l
ls: cannot access |: No such file or directory
ls: cannot access wc: No such file or directory

what is the correct way to store the command in variable like below commands. or give me any alternate suggestion to fulfill my requirement.

cat passwd.txt|tr ' ' '\n'|egrep -i "$pwd_in"|sort
mklement0
  • 382,024
  • 64
  • 607
  • 775
Sriram P
  • 179
  • 1
  • 13
  • 5
    Obligatory link: http://mywiki.wooledge.org/BashFAQ/050 – Tom Fenech Apr 13 '17 at 13:23
  • 1
    Really **double read** the above link. Storing commands in variables, are in nearly all cases wrong idea... – clt60 Apr 13 '17 at 13:30
  • @tripleee: This question is about wanting to echo an arbitrary command line before executing it. The linked question is about why quotes that result from shell expansions do not have syntactical meaning. While there is some relationship, they're not duplicates. – mklement0 Apr 13 '17 at 14:05
  • 1
    Fair enough; thanks for the feedback. For historical context, the question I suggested as a duplicate was http://stackoverflow.com/questions/12136948/in-bash-why-do-shell-commands-ignore-quotes-in-arguments-when-the-arguments-are – tripleee Apr 13 '17 at 16:30

4 Answers4

4

Put your command in a function and enable xtrace.

cmd ()
{
    set -x
    ls -ltr | wc -l
    set +x
}
ceving
  • 21,900
  • 13
  • 104
  • 178
3

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.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
0

Following is working on Bash. Try this:

var="ls -ltr|wc -l";
echo -e "$var \n `eval $var`";

Output:
ls -ltr|wc -l 
10
toxic_boi_8041
  • 1,424
  • 16
  • 26
0

Bonus option worth considering if speed isn't a concern:

(set -x; ls -l "$PWD")

This will spawn a subshell that inherits all the properties of the parent shell, but the nice thing is you don't have to worry about having to clean up with set +x at the end of every statement you want to print.

Aaron_H
  • 1,623
  • 1
  • 12
  • 26