value How to set a variable to the output of a command in Bash?




shell script assign command output to variable (11)

I have a pretty simple script that is something like the following:

#!/bin/bash

VAR1="$1"    
MOREF='sudo run command against $VAR1 | grep name | cut -c7-'

echo $MOREF

When I run this script from the command line and pass it the arguments, I am not getting any output. However, when I run the commands contained within the $MOREF variable, I am able to get output.

I would like to know how one can take the results of a command that needs to be run within a script, save it to a variable, and then output that variable on the screen?


Some may find this useful. Integer values in variable substitution, where the trick is using $(()) double brackets:

N=3
M=3
COUNT=$N-1
ARR[0]=3
ARR[1]=2
ARR[2]=4
ARR[3]=1

while (( COUNT < ${#ARR[@]} ))
do
  ARR[$COUNT]=$((ARR[COUNT]*M))
  (( COUNT=$COUNT+$N ))
done

When setting a variable make sure you have NO Spaces before and/or after the = sign. Literally spent an hour trying to figure this, trying all kinds of solutions! This is Not cool.

Correct:

WTFF=`echo "stuff"`
echo "Example: $WTFF"

Will Fail with error: (stuff: not found or similar)

WTFF= `echo "stuff"`
echo "Example: $WTFF"

Some bash tricks I use to set variables from commands

2nd Edit 2018-02-12: Adding a special way, see at very bottom of this!

2018-01-25 Edit: add sample function (for populating vars about disk usage)

First simple old and compatible way

myPi=`echo '4*a(1)' | bc -l`
echo $myPi 
3.14159265358979323844

Mostly compatible, second way

As nesting could become heavy, parenthesis was implemented for this

myPi=$(bc -l <<<'4*a(1)')

Nested sample:

SysStarted=$(date -d "$(ps ho lstart 1)" +%s)
echo $SysStarted 
1480656334

reading more than one variable (with bashisms)

df -k /
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/dm-0         999320 529020    401488  57% /

If I just want Used value:

array=($(df -k /))

you could see array variable:

declare -p array
declare -a array='([0]="Filesystem" [1]="1K-blocks" [2]="Used" [3]="Available" [
4]="Use%" [5]="Mounted" [6]="on" [7]="/dev/dm-0" [8]="999320" [9]="529020" [10]=
"401488" [11]="57%" [12]="/")'

Then:

echo ${array[9]}
529020

But I prefer this:

{ read foo ; read filesystem size used avail prct mountpoint ; } < <(df -k /)
echo $used
529020

1st read foo will just skip header line (variable $foo will contain something like Filesystem 1K-blocks Used Available Use% Mounted on)

Sample function for populating some variables:

#!/bin/bash

declare free=0 total=0 used=0

getDiskStat() {
    local foo
    {
        read foo
        read foo total used free foo
    } < <(
        df -k ${1:-/}
    )
}

getDiskStat $1
echo $total $used $free

Nota: declare line is not required, just for readability.

About sudo cmd | grep ... | cut ...

shell=$(cat /etc/passwd | grep $USER | cut -d : -f 7)
echo $shell
/bin/bash

(Please avoid useless cat! So this is just 1 fork less:

shell=$(grep $USER </etc/passwd | cut -d : -f 7)

All pipes (|) implies forks. Where another process have to be run, accessing disk, libraries calls and so on.

So using sed for sample, will limit subprocess to only one fork:

shell=$(sed </etc/passwd "s/^$USER:.*://p;d")
echo $shell

And with bashisms:

But for many actions, mostly on small files, bash could do the job himself:

while IFS=: read -a line ; do
    [ "$line" = "$USER" ] && shell=${line[6]}
  done </etc/passwd
echo $shell
/bin/bash

or

while IFS=: read loginname encpass uid gid fullname home shell;do
    [ "$loginname" = "$USER" ] && break
  done </etc/passwd
echo $shell $loginname ...

Going further about variable splitting...

Have a look at my answer to How do I split a string on a delimiter in Bash?

Alternative: reducing forks by using background long-running tasks

2nd Edit 2018-02-12: In order to prevent multiple forks like

myPi=$(bc -l <<<'4*a(1)'
myRay=12
myCirc=$(bc -l <<<" 2 * $myPi * $myRay ")

or

myStarted=$(date -d "$(ps ho lstart 1)" +%s)
mySessStart=$(date -d "$(ps ho lstart $$)" +%s)

And because date and bc could work line by line:

bc -l <<<$'3*4\n5*6'
12
30

date -f - +%s < <(ps ho lstart 1 $$)
1516030449
1517853288

We could use long running background process to make jobs repetitively, without having to initiate new fork for each request:

mkfifo /tmp/myFifoForBc
exec 5> >(bc -l >/tmp/myFifoForBc)
exec 6</tmp/myFifoForBc
rm /tmp/myFifoForBc

(of course, FD 5 and 6 have to be unused!)... From there, you could use this process by:

echo "3*4" >&5
read -u 6 foo
echo $foo
12

echo >&5 "pi=4*a(1)"
echo >&5 "2*pi*12"
read -u 6 foo
echo $foo
75.39822368615503772256

Into a function newConnector

You may found my newConnector function on GitHub.Com or on my own site (Nota on github, there is two files, on my site, function and demo are bundled into 1 file wich could be sourced for use or just run for demo)

Sample:

. shell_connector.sh

tty
/dev/pts/20

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30745 pts/20   R+     0:00  \_ ps --tty pts/20 fw

newConnector /usr/bin/bc "-l" '3*4' 12

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30944 pts/20   S      0:00  \_ /usr/bin/bc -l
  30952 pts/20   R+     0:00  \_ ps --tty pts/20 fw

declare -p PI
bash: declare: PI: not found

myBc '4*a(1)' PI
declare -p PI
declare -- PI="3.14159265358979323844"

The function myBc let you use the background task with simple syntax, and for date:

newConnector /bin/date '-f - +%s' @0 0
myDate '2000-01-01'
  946681200
myDate "$(ps ho lstart 1)" boottime
myDate now now ; read utm idl </proc/uptime
myBc "$now-$boottime" uptime
printf "%s\n" ${utm%%.*} $uptime
  42134906
  42134906

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30944 pts/20   S      0:00  \_ /usr/bin/bc -l
  32615 pts/20   S      0:00  \_ /bin/date -f - +%s
   3162 pts/20   R+     0:00  \_ ps --tty pts/20 fw

From there, if you want to end one of background process, you just have to close his fd:

eval "exec $DATEOUT>&-"
eval "exec $DATEIN>&-"
ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
   4936 pts/20   Ss     0:00 bash
   5256 pts/20   S      0:00  \_ /usr/bin/bc -l
   6358 pts/20   R+     0:00  \_ ps --tty pts/20 fw

wich is not needed, because all fd close when main process finish.


As they have already indicated to you, you should use 'backticks'.

The alternative proposed $(command) works as well, and it also easier to read, but note that it is valid only with bash or korn shells (and shells derived from those), so if your scripts have to be really portable on various Unix systems, you should prefer the old backticks notation.


You need to use either

$(command-here)

or

`command-here`

example

#!/bin/bash

VAR1="$1"
VAR2="$2"

MOREF="$(sudo run command against "$VAR1" | grep name | cut -c7-)"

echo "$MOREF"

You can use back-ticks(also known as accent graves) or $(). Like as-

OUTPUT=$(x+2);
OUTPUT=`x+2`;

Both have the same effect. But OUTPUT=$(x+2) is more readable and the latest one.


Update (2018): the right way is

$(sudo run command)

You're using the wrong kind of apostrophe. You need `, not '. This character is called "backticks" (or "grave accent").

Like this:

#!/bin/bash

VAR1="$1"
VAR2="$2"

MOREF=`sudo run command against "$VAR1" | grep name | cut -c7-`

echo "$MOREF"

This is another way, good to use with some text editors that are unable to correctly highlight every intricate code you create.

read -r -d '' str < <(cat somefile.txt)
echo "${#str}"
echo "$str"

In addition to backticks (`command`), you can use $(command), which I find easier to read, and allows for nesting.

OUTPUT="$(ls -1)"
echo "${OUTPUT}"

Quoting (") does matter to preserve multi-line values.


If you want to do it with multiline/multiple command/s then you can do this:

output=$( bash <<EOF
#multiline/multiple command/s
EOF
)

Or:

output=$(
#multiline/multiple command/s
)

Example:

#!/bin/bash
output="$( bash <<EOF
echo first
echo second
echo third
EOF
)"
echo "$output"

Output:

first
second
third

Using heredoc you can simplify things pretty easily by breaking down your long single line code into multiline one. Another example:

output="$( ssh -p $port [email protected]$domain <<EOF 
#breakdown your long ssh command into multiline here.
EOF
)"

Just to be different:

MOREF=$(sudo run command against $VAR1 | grep name | cut -c7-)






command-line