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.
How can one 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?
echo
the variable is a useless use of echo
, and a useless use of variables. - anyone variable=$(command)
but I think "$string"
is a valid command
"; stackoverflow.com/questions/37194795/… - anyone 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.
Answered 2023-09-20 20:32:16
echo
command. - anyone ${OUTPUT}foo
. They are also required when performing inline string operations on the variable, such as ${OUTPUT/foo/bar}
- anyone $(sudo run command)
If you're going to use an apostrophe, you need `
, not '
. This character is called "backticks" (or "grave accent"):
#!/bin/bash
VAR1="$1"
VAR2="$2"
MOREF=`sudo run command against "$VAR1" | grep name | cut -c7-`
echo "$MOREF"
Answered 2023-09-20 20:32:16
echo
. - anyone $()
works. Couldn't fix until I saw this page. - anyone backticks
, but not $(...) notation. So you need to use backticks if you require compatibility with older Unix systems. - anyone Sorry, there is a loong answer. But as bash is a shell, where the main goal is to run other unix commands and react on result code and/or output ( commands are often piped filter, etc... ), storing command output in variables is something basic and fundamental.
Therefore, depending on
You could look at showCert
function - a complex sample, parsing openssl
output for building: 1 associative array for parsing SUBJECT
field, 1 standard array for parsing alternatives names
and storing dates to UNIXEPOCH. (Using a single fork to date
command for converting two dates together) - In How to determine SSL cert expiration date from a PEM certificate?
myPi=`echo '4*a(1)' | bc -l`
echo $myPi
3.14159265358979323844
As nesting could become heavy, parenthesis was implemented for this
myPi=$(bc -l <<<'4*a(1)')
Using backticks in script is to be avoided today.
Nested sample:
SysStarted=$(date -d "$(ps ho lstart 1)" +%s)
echo $SysStarted
1480656334
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
{ read -r _;read -r filesystem size using avail prct mountpoint ; } < <(df -k /)
echo $using
529020
( The first read _
will just drop header line. ) Here, in only one command, you will populate 6 different variables (shown by alphabetical order):
declare -p avail filesystem mountpoint prct size using
declare -- avail="401488"
declare -- filesystem="/dev/dm-0"
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 _ ; 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")
(Note Used
and Blocks
is switched there: read ... dsk[6] dsk[2] dsk[9] ...
)
... will work with associative arrays too: read _ disk[total] disk[used] ...
Other related sample: Parsing xrandr
output: and end of Firefox tab by bash in a size of x% of display size? or at AskUbuntu.com Parsing xrandr
output
fd
using unnamed fifo:There is an elegent way! In this sample, I will read /etc/passwd
file:
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 $home
done {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 ... 65534
echo -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 or with the output of a command:
{
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;baz
alice;bob;charlie
$cherry;$strawberry;$memberberries
eof
As this answer is loong enough, for this paragraph,
I just will let you refer to
this answer to How to parse a CSV file in Bash?
, I read a file by using an unnamed fifo, using syntax like:
exec {FD}<"$file" # open unnamed fifo for read
IFS=';' read -ru $FD -a headline
while IFS=';' read -ru $FD -a row ;do ...
... But using bash loadable CSV module.
On my website, you may find the same script, reading CSV
as inline document.
#!/bin/bash
declare free=0 total=0 used=0 mpnt='??'
getDiskStat() {
{
read _
read _ total used free _ mpnt
} < <(
df -k ${1:-/}
)
}
getDiskStat $1
echo "$mpnt: Tot:$total, used: $used, free: $free."
Nota: declare
line is not required, just for readability.
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
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/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 ...
Have a look at my answer to How do I split a string on a delimiter in Bash?
In order to prevent multiple forks like
myPi=$(bc -l <<<'4*a(1)'
myRay=12
myCirc=$(bc -l <<<" 2 * $myPi * $myRay ")
or to obtain system start time and current shell start time, both as UNIX EPOCH, I could do two nested forks:
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 building my two variables: $myStarted
and $mySessStart
could be done in one operation:
{
read -r myStarted
read -r mySessStart
} < <(
date -f - +%s < <(
ps ho lstart 1 $$
)
)
could be written on one line:
{ read -r myStarted;read -r mySessStart;}< <(date -f- +%s< <(ps ho lstart 1 $$))
But we could use a long running background process to make as many request we need, without having to initiate a new fork for each request.
You could have a look how reducing forks make Mandelbrot bash, improve from more than eight hours to less than five seconds.
Under bash, there is a built-in function: coproc
:
coproc bc -l
echo 4*3 >&${COPROC[1]}
read -u $COPROC answer
echo $answer
12
echo >&${COPROC[1]} 'pi=4*a(1)'
ray=42.0
printf >&${COPROC[1]} '2*pi*%s\n' $ray
read -u $COPROC answer
echo $answer
263.89378290154263202896
printf >&${COPROC[1]} 'pi*%s^2\n' $ray
read -u $COPROC answer
echo $answer
5541.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!
The little extra: (Little but powerful!) While bc
stay running, they will hold all his registers. So variables or functions could be defined at initialisation step, as first write to ${COPROC[1]}
, just after starting the task (... or even at any time).
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 unique file which could be sourced for use or just run for demo.)
Sample:
source 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.
Then 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 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.
Answered 2023-09-20 20:32:16
EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)")
was the statement I was looking for. - anyone echo
; you want simply grep "$CONTAINER_NAME"
- anyone tput
as background task for color rendering on terminal!! - anyone read _
to drop not only skip... and so... Answer edited, added link to CSV parser sample. Thanks! - anyone 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 KornShell (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.
Answered 2023-09-20 20:32:16
$()
is fully compatible with POSIX sh, as standardized over two decades ago. - anyone /bin/sh
on Solaris 10 still does not recognize $(…)
— and AFAIK that's true on Solaris 11 too. - anyone /bin/sh
is ksh93
. - anyone $()
in the POSIX shell on HP-UX for the past 10+ years. - anyone I know three ways to do it:
Functions are suitable for such tasks:**
func (){
ls -l
}
Invoke it by saying func
.
Also another suitable solution could be eval:
var="ls -l"
eval $var
The third one is using variables directly:
var=$(ls -l)
OR
var=`ls -l`
You can get the output of the third solution in a good way:
echo "$var"
And also in a nasty way:
echo $var
Answered 2023-09-20 20:32:16
"$var"
good and $var
nasty? - anyone Just to be different:
MOREF=$(sudo run command against $VAR1 | grep name | cut -c7-)
Answered 2023-09-20 20:32:16
When setting a variable make sure you have no spaces before and/or after the =
sign. I literally spent an hour trying to figure this out, 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"
Answered 2023-09-20 20:32:16
var=value somecommand
runs somecommand
with var
in its environment having the value value
. Thus, var= somecommand
is exporting var
in the environment of somecommand
with an empty (zero-byte) value. - anyone 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 a multiline one. Another example:
output="$( ssh -p $port $user@$domain <<EOF
# Breakdown your long ssh command into multiline here.
EOF
)"
Answered 2023-09-20 20:32:16
bash
inside the command substitution? You are already creating a subshell by the command substitution itself. If you want to put multiple commands, just separate them by newline or semicolon. output=$(echo first; echo second; ...)
- anyone 'bash -c "bash -c \"bash -c ...\""'
would be "different", too; but I don't see the point of that. - anyone ssh
sudo -s
executing mysql commands inside, etc.. (instead of bash) - anyone variable=$(bash -c 'echo "foo"; echo "bar"')
over variable=$(echo "foo"; echo "bar")
-- the here document is just a quoting mechanism and doesn't really add anything except another useless complication. - anyone ssh -p $port $user@$domain /bin/bash <<EOF
in order to prevent Pseudo-terminal will not be allocated because stdin is not a terminal.
warning - anyone You need to use either
$(command-here)
or
`command-here`
#!/bin/bash
VAR1="$1"
VAR2="$2"
MOREF="$(sudo run command against "$VAR1" | grep name | cut -c7-)"
echo "$MOREF"
Answered 2023-09-20 20:32:16
$()
is much better than backticks. See: What is the benefit of using $() instead of backticks in shell scripts? - anyone If the command that you are trying to execute fails, it would write the output onto the error stream and would then be printed out to the console.
To avoid it, you must redirect the error stream:
result=$(ls -l something_that_does_not_exist 2>&1)
Answered 2023-09-20 20:32:16
This is another way and is 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"
Answered 2023-09-20 20:32:16
Mac/OSX nowadays come with old Bash versions, ie GNU bash, version 3.2.57(1)-release (arm64-apple-darwin21)
. In this case, one can use:
new_variable="$(some_command)"
A concrete example:
newvar="$(echo $var | tr -d '123')"
Note the ()
, instead of the usual {}
in Bash 4.
Answered 2023-09-20 20:32:16
You can use backticks (also known as accent graves) or $()
.
Like:
OUTPUT=$(x+2);
OUTPUT=`x+2`;
Both have the same effect. But OUTPUT=$(x+2) is more readable and the latest one.
Answered 2023-09-20 20:32:16
x+2
is not a valid command, most places. To the extent that this isn't misleading beginners to think this is how you do arithmetic, this duplicates existing answers. - anyone 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.
The following assigns harshil
to L
and then prints it
L=$"harshil"
echo "$L"
The following assigns the output of the command tr
to L2. tr
is being operated on another variable, L1.
L2=$(echo "$L1" | tr [:upper:] [:lower:])
Answered 2023-09-20 20:32:16
$"..."
probably doesn't do what you think it does. 2. This is already given in Andy Lester's answer. - anyone echo ${L1,,}
to downcase, or echo ${L1^^}
to upcase. - anyone 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
Answered 2023-09-20 20:32:16
for ((...))
loop would seem like a better match for the loop variable). Also, you should not use uppercase for your private variables. - anyone ARR=(3 2 4 1);for((N=3,M=3,COUNT=N-1;COUNT < ${#ARR[@]};ARR[COUNT]*=M,COUNT+=N)){ :;}
but I agree with @tripleee: I don't understand what do this, there! - anyone If you use bash
: For the case you have multiple commands via pipes and need both their results and their exit statuses there is another great solution. It should be clear from the following example:
# This saves all the results into the RES variable
grep -E "\S" "file.txt" | sort | uniq | read -d '' RES
# 'read' exit status 1 means all input was read till EOF, we're OK with that
if (( PIPESTATUS[0] > 1 || PIPESTATUS[1] > 0 || PIPESTATUS[2] > 0 || PIPESTATUS[3] > 1 )); then
echo "ERROR"
else
echo "$RES"
fi
In order for this to work you need to enable shopt -s lastpipe
(works just for bash
AFAIK!), plus if you're in an interactive session you need to disable job control via set +m
(not needed in scripts by default).
See this post in another thread for more details.
Answered 2023-09-20 20:32:16