How do I set a variable to the output of a command in Bash? How do I set a variable to the output of a command in Bash? bash bash

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


In addition to backticks `command`, command substitution can be done with $(command) or "$(command)", which I find easier to read, and allows for nesting.

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

Quoting (") does matter to preserve multi-line variable values; it is optional on the right-hand side of an assignment, as word splitting is not performed, so OUTPUT=$(ls -1) would work fine.


$(sudo run command)

If you're going to use an apostrophe, you need `, not '. This character is called "backticks" (or "grave accent"):

#!/bin/bashVAR1="$1"VAR2="$2"MOREF=`sudo run command against "$VAR1" | grep name | cut -c7-`echo "$MOREF"


Some Bash tricks I use to set variables from commands

Sorry, there is a loong answer, but as is a , where the main goal is to run other commands and react to resut code and/or output, ( commands are often piped filter, etc... ).

Storing command output in variables is something basic and fundamental.

Therefore, depending on

  • compatibility ()
  • kind of output (filter(s))
  • number of variable to set (split or interpret)
  • execution time (monitoring)
  • error trapping
  • repeatability of request (see long running background process, further)
  • interactivity (considering user input while reading from another input file descriptor)
  • do I miss something?

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

features

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 a used value:

array=($(df -k /))

you could see an array variable:

declare -p arraydeclare -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 often use this:

{ read -r _;read -r filesystem size using avail prct mountpoint ; } < <(df -k /)echo $using529020

The first read foo will just skip header line, but in only one command, you will populate 7 different variables:

declare -p avail filesystem foo mountpoint prct size usingdeclare -- avail="401488"declare -- filesystem="/dev/dm-0"declare -- foo="Filesystem     1K-blocks   Used Available Use% Mounted on"declare -- mountpoint="/"declare -- prct="57%"declare -- size="999320"declare -- using="529020"

Or

{ read -a head;varnames=(${head[@]//[K1% -]});varnames=(${head[@]//[K1% -]});  read ${varnames[@],,} ; } < <(LANG=C df -k /)

Then:

declare -p varnames ${varnames[@],,} declare -a varnames=([0]="Filesystem" [1]="blocks" [2]="Used" [3]="Available" [4]="Use" [5]="Mounted" [6]="on")declare -- filesystem="/dev/dm-0"declare -- blocks="999320"declare -- used="529020"declare -- available="401488"declare -- use="57%"declare -- mounted="/"declare -- on=""

Or even:

{ read foo ; read filesystem dsk[{6,2,9}] prct mountpoint ; } < <(df -k /)declare -p mountpoint dskdeclare -- mountpoint="/"declare -a dsk=([2]="529020" [6]="999320" [9]="401488")

(Note Used and Blocks is switched there: read ... dsk[6] dsk[2] dsk[9] ...)

... will work with associative arrays too: read foo disk[total] disk[used] ...

Dedicated fd using unnamed fifo:

There is an elegent way:

users=()while IFS=: read -u $list user pass uid gid name home bin ;do    ((uid>=500)) &&        printf -v users[uid] "%11d %7d %-20s %s\n" $uid $gid $user $homedone {list}</etc/passwd

Using this way (... read -u $list; ... {list}<inputfile) leave STDIN free for other purposes, like user interaction.

Then

echo -n "${users[@]}"       1000    1000 user         /home/user...      65534   65534 nobody       /nonexistent

and

echo ${!users[@]}1000 ... 65534echo -n "${users[1000]}"      1000    1000 user       /home/user

This could be used with static files or even /dev/tcp/xx.xx.xx.xx/yyy with x for ip address or hostname and y for port number:

{    read -u $list -a head          # read header in array `head`    varnames=(${head[@]//[K1% -]}) # drop illegal chars for variable names    while read -u $list ${varnames[@],,} ;do        ((pct=available*100/(available+used),pct<10)) &&            printf "WARN: FS: %-20s on %-14s %3d <10 (Total: %11u, Use: %7s)\n" \                "${filesystem#*/mapper/}" "$mounted" $pct $blocks "$use"     done } {list}< <(LANG=C df -k)

And of course with inline documents:

while IFS=\; read -u $list -a myvar ;do    echo ${myvar[2]}done {list}<<"eof"foo;bar;bazalice;bob;charlie$cherry;$strawberry;$memberberrieseof

Sample function for populating some variables:

#!/bin/bashdeclare free=0 total=0 used=0getDiskStat() {    local foo    {        read foo        read foo total used free foo    } < <(        df -k ${1:-/}    )}getDiskStat $1echo $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 one 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 itself:

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

or

while IFS=: read loginname encpass uid gid fullname home shell;do    [ "$loginname" = "$USER" ] && break  done </etc/passwdecho $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 backgrounded long-running tasks

In order to prevent multiple forks like

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

or

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

This work fine, but running many forks is heavy and slow.

And commands like date and bc could make many operations, line by line!!

See:

bc -l <<<$'3*4\n5*6'1230date -f - +%s < <(ps ho lstart 1 $$)15160304491517853288

So we could use a long running background process to make many jobs, without having to initiate a new fork for each request.

Under , there is a built-in function: coproc:

coproc bc -lecho 4*3 >&${COPROC[1]}read -u $COPROC answerecho $answer12echo >&${COPROC[1]} 'pi=4*a(1)'ray=42.0printf >&${COPROC[1]} '2*pi*%s\n' $rayread -u $COPROC answerecho $answer263.89378290154263202896printf >&${COPROC[1]} 'pi*%s^2\n' $rayread -u $COPROC answerecho $answer5541.76944093239527260816

As bc is ready, running in background and I/O are ready too, there is no delay, nothing to load, open, close, before or after operation. Only the operation himself! This become a lot quicker than having to fork to bc for each operation!

Border effect: While bc stay running, they will hold all registers, so some variables or functions could be defined at initialisation step, as first write to ${COPROC[1]}, just after starting the task (via coproc).

Into a function newConnector

You may found my newConnector function on GitHub.Com or on my own site (Note on GitHub: there are two files on my site. Function and demo are bundled into one uniq file which could be sourced for use or just run for demo.)

Sample:

source shell_connector.shtty/dev/pts/20ps --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 fwnewConnector /usr/bin/bc "-l" '3*4' 12ps --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 fwdeclare -p PIbash: declare: PI: not foundmyBc '4*a(1)' PIdeclare -p PIdeclare -- PI="3.14159265358979323844"

The function myBc lets you use the background task with simple syntax.

Then for date:

newConnector /bin/date '-f - +%s' @0 0myDate '2000-01-01'  946681200myDate "$(ps ho lstart 1)" boottimemyDate now nowread utm idl </proc/uptimemyBc "$now-$boottime" uptimeprintf "%s\n" ${utm%%.*} $uptime  42134906  42134906ps --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 processes, you just have to close its 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

which is not needed, because all fd close when the main process finishes.