bash - value - shell script assign command output to variable




How to set a variable to the output of a command in Bash? (9)

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.

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?


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"

Here are two more ways:

Please keep in mind that space is very important in bash. So, if you want your command to run, use as is without introducing any more spaces.

  1. following assigns harshil to L and then prints it

    L=$"harshil"
    echo "$L"
    
  2. following assigns the output of the command tr to L2. tr is being operated on another variable L1.

    L2=$(echo "$L1" | tr [:upper:] [:lower:])
    

I know three ways to do:

1) Functions are suitable for such tasks:

func (){
ls -l
}

Invoke it by saying func

2) Also another suitable solution could be eval:

var="ls -l"
eval $var

3) The third one is using variables directly:

var=$(ls -l)
OR
var=`ls -l`

you can get output of third solution in good way:

echo "$var"

and also in nasty way:

echo $var

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.


Just to be different:

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

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"

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"

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"




command-line