Friday, June 28, 2019

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

2nd Edit 2018-02-12: Added a different way, search at the bottom of this for long-running tasks!



2018-01-25 Edit: added a sample function (for populating variables 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 a used value:




array=($(df -k /))


you could see an 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 using avail prct mountpoint ; } < <(df -k /)

echo $using
529020


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 using
declare -- 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 even:



{ read foo ; read filesystem dsk[{6,2,9}] prct mountpoint ; } < <(df -k /)
declare -p mountpoint dsk

declare -- mountpoint="/"
declare -a dsk=([2]="529020" [6]="999320" [9]="401488")


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



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 one fork less:



shell=$(grep $USER 



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 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 echo $shell
/bin/bash


or




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


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'
12
30

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

1517853288


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



We just need some file descriptors and fifos for doing this properly:



mkfifo /tmp/myFifoForBc
exec 5> >(bc -l >/tmp/myFifoForBc)
exec 6
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 (Note on GitHub: there are two files on my site. Function and demo are bundled into one file which 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 lets 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 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 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.

No comments:

Post a Comment

plot explanation - Why did Peaches&#39; mom hang on the tree? - Movies &amp; TV

In the middle of the movie Ice Age: Continental Drift Peaches' mom asked Peaches to go to sleep. Then, she hung on the tree. This parti...