如何将变量设置为Bash中命令的输出?

我有一个非常简单的脚本,如下所示:

#!/bin/bash
VAR1="$1"MOREF='sudo run command against $VAR1 | grep name | cut -c7-'
echo $MOREF

当我从命令行运行此脚本并将参数传递给它时,我没有得到任何输出。但是,当我运行$MOREF变量中包含的命令时,我能够获得输出。

如何获取需要在脚本中运行的命令的结果,将其保存到变量中,然后在屏幕上输出该变量?

2559375 次浏览
$(sudo run command)

如果你要使用撇号,你需要`,而不是'。这个字符被称为“反引号”(或“严重口音”):

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

除了反引号`command`命令替换可以用$(command)"$(command)"完成,我发现这更容易阅读,并允许嵌套。

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

引用(")对保留多行变量值很重要;它在赋值的右侧是可选的,就像不执行单词拆分一样,所以OUTPUT=$(ls -1)可以正常工作。

只是为了与众不同:

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

正如他们已经向您表明的那样,您应该使用“反引号”。

提议的替代方案$(command)也有效,并且更易于阅读,但请注意,它仅适用于Bash或KornShell(以及从中派生的shell),因此,如果您的脚本必须在各种Unix系统上真正可移植,您应该更喜欢旧的反引号符号。

我知道有三种方法可以做到:

  1. 函数适用于这样的任务:**

    func (){ls -l}

    通过func来调用它。

  2. 另一个合适的解决方案可以是ava:

    var="ls -l"eval $var
  3. 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"

而且以一种令人讨厌的方式:

echo $var

这是另一种方式,适用于一些无法正确突出显示您创建的每个复杂代码的文本编辑器:

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

如果您想使用多行/多个命令/s执行此操作,那么您可以这样做:

output=$( bash <<EOF# Multiline/multiple command/sEOF)

或:

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

示例:

#!/bin/bashoutput="$( bash <<EOFecho firstecho secondecho thirdEOF)"echo "$output"

输出:

firstsecondthird

使用heldoc,您可以通过将长单行代码分解为多行代码来轻松简化事情。另一个例子:

output="$( ssh -p $port $user@$domain <<EOF# Breakdown your long ssh command into multiline here.EOF)"

有些人可能会觉得这很有用。变量替换中的整数值,技巧是使用$(())双括号:

N=3M=3COUNT=$N-1ARR[0]=3ARR[1]=2ARR[2]=4ARR[3]=1
while (( COUNT < ${#ARR[@]} ))doARR[$COUNT]=$((ARR[COUNT]*M))(( COUNT=$COUNT+$N ))done

您可以使用反引号(也称为重音坟墓)或$()

喜欢:

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

两者都有相同的效果。但是OUTPUT=$(x+2)更具可读性,也是最新的。

这里还有两种方法:

请记住,空间在Bash中非常重要。因此,如果您希望您的命令运行,请按原样使用,而不会引入任何更多的空格。

  1. 下面将harshil分配给L,然后打印它

    L=$"harshil"echo "$L"
  2. 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:])

我使用的一些Bash技巧来设置命令中的变量

对不起,有一个长期的答案,但,其中主要目标是运行其他命令并对结果代码和/或输出做出反应,(命令通常是管道过滤器等)。

将命令输出存储在变量中是基本和基本的东西。

因此,取决于

  • 兼容性(
  • 输出类型(过滤器)
  • 要设置的变量数(拆分或解释)
  • 执行时间(监测)
  • 错误捕获
  • 请求的可重复性(请参阅长时间运行的后台进程,进一步)
  • 交互性(在读取另一个输入文件描述符时考虑用户输入)
  • 我错过什么了吗?

第一个简单,旧(过时)和兼容的方式

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

兼容,第二种方式

由于嵌套可能变得很重,为此实现了括号

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

今天要避免在脚本中使用反引号。

嵌套示例:

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

功能

读取多个变量(巴什主义

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

如果我只想要一个使用值:

array=($(df -k /))

你可以看到一个阵列变量:

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]="/")'

然后:

echo ${array[9]}529020

我经常使用这个:

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

(第一个read _将只是下降标题行。)在这里,在只有一个命令中,您将填充6个不同个变量(按字母顺序显示):

declare -p avail filesystem mountpoint prct size usingdeclare -- avail="401488"declare -- filesystem="/dev/dm-0"declare -- mountpoint="/"declare -- prct="57%"declare -- size="999320"declare -- using="529020"

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

然后:

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=""

甚至:

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

(注UsedBlocks在那里切换:read ... dsk[6] dsk[2] dsk[9] ...

…也将与关联数组一起工作:read _ disk[total] disk[used] ...

使用无名FIFO专用#0

有一种优雅的方式!在这个示例中,我将读取/etc/passwd文件:

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

使用这种方式(... read -u $list; ... {list}<inputfile)让#1可以用于其他目的,例如用户交互。

然后

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

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

这可以用于静态文件,甚至/dev/tcp/xx.xx.xx.xx/yyyx用于ip地址或主机名,y用于端口号或命令的输出:

{read -u $list -a head          # read header in array `head`varnames=(${head[@]//[K1% -]}) # drop illegal chars for variable nameswhile 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)

当然是内联文档

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

解析CSV文件的实用示例:

由于这个答案足够长,对于这一段,我只会让你参考这个答案#0,我使用无名FIFO读取文件,使用语法如:

exec {FD}<"$file"   # open unnamed fifo for readIFS=';' read -ru $FD -a headlinewhile IFS=';' read -ru $FD -a row ;do ...

…但使用可加载CSV模块

在我的网站上,你可以找到相同的脚本,读取#0作为内联文档

用于填充一些变量的示例函数:

#!/bin/bash
declare free=0 total=0 used=0 mpnt='??'
getDiskStat() \{\{read _read _ total used free _ mpnt} < <(df -k ${1:-/})}
getDiskStat $1echo "$mpnt: Tot:$total, used: $used, free: $free."

注意:declare行不是必需的,只是为了易读性。

关于sudo cmd | grep ... | cut ...

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

(请避免无用的cat!所以这只是少了一个分叉:

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

所有管道(|)意味着分叉。必须运行另一个进程、访问磁盘、库调用等。

因此,使用sed作为示例,将子进程限制为只有一个

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

对于巴什主义

但是对于许多操作,主要是在小文件上,Bash可以自己完成这项工作:

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

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

再往前走变量分裂

看看我的答案如何在Bash中的分隔符上拆分字符串?

替代方案:使用长时间运行任务背景化减少叉子

为了防止多个分叉,如

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

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

这项工作很好,但运行许多分叉是沉重和缓慢的。

datebc这样的命令可以进行许多操作,一行一行!!

见:

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

因此,我们可以使用长时间运行的后台进程来完成许多作业,而不必为每个请求启动一个新的

你可以看看减少分叉如何使Mandelbrot bash,从超过8小时提高到不到5秒。

下,有一个内置函数:coproc

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

由于bc已准备就绪,后台运行和I/O也已准备就绪,因此没有延迟,在操作之前或之后没有任何要加载、打开、关闭的内容。只有操作本身!这比必须为每个操作分叉到bc快得多!

边界效应:当bc保持运行时,它们将保存所有寄存器,因此可以在初始化步定义一些变量或函数,因为首先写入${COPROC[1]},就在启动任务之后(通过coproc)。

进入函数newConnector

您可能会在我自己的网站GitHub. Com或上找到我的newConnector函数(在GitHub上注意:我的网站上有两个文件。函数和演示捆绑在一个唯一的文件中,可以来源以供使用或仅用于演示。)

样本:

source shell_connector.sh
tty/dev/pts/20
ps --tty pts/20 fwPID TTY      STAT   TIME COMMAND29019 pts/20   Ss     0:00 bash30745 pts/20   R+     0:00  \_ ps --tty pts/20 fw
newConnector /usr/bin/bc "-l" '3*4' 12
ps --tty pts/20 fwPID TTY      STAT   TIME COMMAND29019 pts/20   Ss     0:00 bash30944 pts/20   S      0:00  \_ /usr/bin/bc -l30952 pts/20   R+     0:00  \_ ps --tty pts/20 fw
declare -p PIbash: declare: PI: not found
myBc '4*a(1)' PIdeclare -p PIdeclare -- PI="3.14159265358979323844"

函数myBc允许您使用简单语法的后台任务。

然后是日期:

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%%.*} $uptime4213490642134906
ps --tty pts/20 fwPID TTY      STAT   TIME COMMAND29019 pts/20   Ss     0:00 bash30944 pts/20   S      0:00  \_ /usr/bin/bc -l32615 pts/20   S      0:00  \_ /bin/date -f - +%s3162 pts/20   R+     0:00  \_ ps --tty pts/20 fw

从那里,如果你想结束一个后台进程,你只需要关闭它的#0

eval "exec $DATEOUT>&-"eval "exec $DATEIN>&-"ps --tty pts/20 fwPID TTY      STAT   TIME COMMAND4936 pts/20   Ss     0:00 bash5256 pts/20   S      0:00  \_ /usr/bin/bc -l6358 pts/20   R+     0:00  \_ ps --tty pts/20 fw

这是不需要的,因为当主进程完成时,所有#0都关闭。

设置变量时,确保在=符号之前和/或之后有没有空格。我花了一个小时试图弄清楚这个问题,尝试各种解决方案!这是没有很酷。

更正:

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

将失败错误:“东西:没有找到”或类似

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

你也得用

$(command-here)

`command-here`

示例

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

如果您尝试执行的命令失败,它会将输出写入错误流,然后打印到控制台。

要避免它,您必须重定向错误流:

result=$(ls -l something_that_does_not_exist 2>&1)

Mac/OSX现在带有旧的Bash版本,即GNU bash, version 3.2.57(1)-release (arm64-apple-darwin21)。在这种情况下,可以使用:

new_variable="$(some_command)"

一个具体的例子:

newvar="$(echo $var | tr -d '123')"

注意(),而不是Bash 4中通常的{}