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


1675

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

#!/bin/bash

VAR1="$1"
MOREF='sudo run command against $VAR1 | grep name | cut -c7-'

echo $MOREF

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

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



40
顺便说一句,POSIX为变量名称定义了全大写变量,这些变量名称对操作系统或外壳程序本身具有意义,而具有至少一个小写字符的名称保留供应用程序使用。因此,请考虑为您自己的shell变量使用小写名称,以避免意外冲突(请注意,设置shell变量将覆盖所有名称相同的环境变量)。
Charles Duffy

1
顺便说一句,捕捉输出到一个变量,所以您可以那么echo这个变量是一个无用的使用echo以及无用的使用变量。
Tripleee '18

1
此外,通常不需要将输出存储在变量中。对于较小的短字符串,您将需要在程序中多次引用,这完全可以,而且是正确的方法。但是要处理大量数据,您需要将流程重塑为管道或使用临时文件。
Tripleee

Answers:


2374

除了反引号之外`command`命令替换还可以使用$(command)或来完成"$(command)",我发现它更易于阅读,并且可以嵌套。

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

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

引用(")对于保留多行变量值很重要;它在作业的右侧是可选的,因为不执行分词,所以OUTPUT=$(ls -1)可以正常工作。


58
我们可以为多行输出提供一些分隔符吗?
Aryan

20
空格(或缺少空格)很重要
Ali

8
@ timhc22,花括号不相关;只有引号是很重要的:扩展结果在传递给echo命令之前是否进行了字符串拆分和glob扩展。
Charles Duffy 2015年

4
啊,谢谢!那么花括号有什么好处吗?
timhc22 2015年

14
当变量后面紧跟着更多的字符(可以解释为变量名称的一部分)时,可以使用大括号。 例如 ${OUTPUT}foo。对变量执行内联字符串操作时也需要它们,例如${OUTPUT/foo/bar}
rich remer '16

282

正确的方法是

$(sudo run command)

如果你打算使用一个单引号,你需要`,没有'。此字符称为“反引号”(或“重音符”)。

像这样:

#!/bin/bash

VAR1="$1"
VAR2="$2"

MOREF=`sudo run command against "$VAR1" | grep name | cut -c7-`

echo "$MOREF"

31
反引号语法已过时,您确实需要在中的变量插值两边加上双引号echo
2015年

10
我要补充一点,在上面的作业中,您必须小心'='周围的空格。您不应在其中有任何空格,否则您将获得不正确的分配
zbstof16年

4
Tripleeee的评论是正确的。在《 cygwin》(2016年5月)中,``在工作时不起作用$()。在看到此页面之前无法修复。
toddwz

2
诸如在Update(2018)上的示例之类的阐述将不胜感激。
爱德华

90

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

2nd Edit 2018-02-12:添加了另一种方式,在此底部搜索长时间运行的任务

2018年1月25日编辑:添加了一个示例函数(用于填充有关磁盘使用情况的变量)

第一种简单,老旧且兼容的方式

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

多数兼容,第二种方式

由于嵌套可能变得很重,因此为此添加了括号

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

嵌套样本:

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

读取多个变量(使用Bashisms

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

如果我只想使用一个值:

array=($(df -k /))

您会看到一个数组变量:

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

然后:

echo ${array[9]}
529020

但是我更喜欢这样:

{ read foo ; read filesystem size using avail prct mountpoint ; } < <(df -k /)
echo $using
529020

第一个read foo将只跳过标题行,但是在一个命令中,您将填充7个不同的变量:

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"

甚至:

{ 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")

...也将与关联数组一起使用:read foo disk[total] disk[used] ...

用于填充一些变量的样本函数:

#!/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

注意: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用于样本,会将子流程限制为仅一个fork

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

Bashisms一起

但是对于许多动作(主要是在小文件上),Bash可以自己完成:

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

要么

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

进一步讨论变量拆分 ...

看一下我如何在Bash中的定界符上分割字符串的答案

备选方案:通过使用后台长时间运行的任务来减少分叉

第2次编辑2018-02-12:

为了防止多叉之类

myPi=$(bc -l <<<'4*a(1)'
myRay=12
myCirc=$(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'
12
30

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

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

我们只需要一些文件描述符fifos即可正确执行此操作:

mkfifo /tmp/myFifoForBc
exec 5> >(bc -l >/tmp/myFifoForBc)
exec 6</tmp/myFifoForBc
rm /tmp/myFifoForBc

(当然,FD,5并且6必须不使用!)...从那里,您可以通过以下方式使用此过程:

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

转化为功能 newConnector

您可以newConnectorGitHub.Com我自己的站点上找到我的函数(在GitHub上注意:我的站点上有两个文件。函数和演示都捆绑在一个文件中,该文件可以供使用或仅用于演示运行。)

样品:

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

该函数myBc使您可以通过简单的语法对日期使用后台任务:

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

从那里开始,如果要结束后台进程之一,则只需关闭其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

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


上面的嵌套样本正是我想要的。可能有一种更简单的方法,但是我正在寻找的是一种方法,以给定其容器在环境变量中的名称,来查找是否已经存在docker容器。所以对我来说:这EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)")就是我要寻找的陈述。
Capricorn1

2
@ capricorn1这是对-的无用用法echo;您只需要grep "$CONTAINER_NAME"
Tripleee


我可能在这里错过了一些东西:kubectl get ns | while read -r line; do echo $ line | grep条款| cut -d''-f1 ; done为每个$line空白行打印出来,然后bash: xxxx: command not found。但是我希望它只会打印出来xxx
papanito

77

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

提出的替代方案也$(command)可以工作,并且也更易于阅读,但是请注意,它仅对Bash或KornShell(以及从它们派生的shell)有效,因此,如果您的脚本必须在各种Unix系统上确实可移植,那么您应该首选旧的反引号符号。


23
他们非常谨慎。POSIX很久以前就不赞成使用反引号。从这个千年开始,大多数shell中都应该使用更现代的语法。(在90年代初,仍然有遗留环境的咳嗽 HP-UX 咳嗽被牢牢地牢牢
抓住

25
不正确 $()与二十多年前标准化的POSIX sh完全兼容。
Charles Duffy 2015年

3
请注意,/bin/sh在Solaris 10上仍然无法识别$(…)-AFAIK在Solaris 11上也是如此。
乔纳森·莱夫勒

2
@JonathanLeffler它实际上是没有更多的利用Solaris 11的情况下/bin/shksh93
jlliagre

2
@tripleee-响应晚了三年:-),但$()过去10多年我一直在HP-UX的POSIX Shell中使用过。
鲍勃·贾维斯

54

我知道三种方法:

  1. 功能适合以下任务:**

    func (){
        ls -l
    }

    通过说来调用它func

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

    var="ls -l"
    eval $var
  3. 第三个是直接使用变量:

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

您可以很好地获得第三个解决方案的输出:

echo "$var"

而且也是一种讨厌的方式:

echo $var

1
前两个问题似乎无法回答当前的问题,而第二个问题通常被认为是可疑的。
人间

1
作为一个刚接触bash的人,为什么"$var"又好又$var讨厌?
彼得


30

只是有所不同:

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

22

设置变量时,请确保在=号之前和/或之后没有空格。从字面上看,花了一个小时试图解决这个问题,尝试各种解决方案!这不酷。

正确:

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

将失败,并显示错误“材料:找不到”或类似内容

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

2
带空格的版本表示不同的东西:在其环境中var=value somecommand运行somecommandvar具有值value。因此,在具有空(零字节)值的环境中进行var= somecommand导出。varsomecommand
查尔斯·达菲

是的,Bash陷阱。
彼得·莫滕森

14

如果要使用多行/多行命令执行此操作,则可以执行以下操作:

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

要么:

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

例:

#!/bin/bash
output="$( bash <<EOF
echo first
echo second
echo third
EOF
)"
echo "$output"

输出:

first
second
third

使用Heredoc,您可以将长长的单行代码分解为多行代码,从而轻松地简化事情。另一个例子:

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

2
bash命令替换中的第二个是什么?您已经通过命令替换本身创建了一个子外壳。如果要放置多个命令,只需用换行符或分号将它们分开。 output=$(echo first; echo second; ...)
2015年

那么同样也'bash -c "bash -c \"bash -c ...\""'将是“不同的”。但我不明白这一点。
2015年

@tripleee heredoc的意义远不止于此。您可以对其他命令ssh sudo -s执行相同的操作,例如在内部执行mysql命令等。(代替bash)
Jahid 2015年

1
我觉得我们的沟通不正确。我在挑战用处variable=$(bash -c 'echo "foo"; echo "bar"')variable=$(echo "foo"; echo "bar")-这里的文件仅仅是一个引用机制并没有真正添加任何东西,除了其他无用的并发症。
tripleee

2
当我在ssh中使用heredoc时,我会精确执行命令ssh -p $port $user@$domain /bin/bash <<EOF以防止出现Pseudo-terminal will not be allocated because stdin is not a terminal.警告
F. Hauri


6

这是另一种方法,可以与某些文本编辑器配合使用,这些文本编辑器无法正确突出显示您创建的每个复杂代码:

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

这不涉及OP的问题,它实际上是关于命令替换而不是进程替换的问题
codeforester

6

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

喜欢:

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

两者具有相同的效果。但是OUTPUT = $(x + 2)更易读并且是最新的。


2
括号是为了允许嵌套而实施的。
F. Hauri

5

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

为了避免这种情况,您必须重定向错误流:

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

4

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

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

1
这似乎与这个问题没有任何关系。如果有人问如何将一个数组中的数字乘以一个常数因子,那将是一个合理的答案,尽管我不记得曾经见过有人问过这个(然后for ((...))循环看起来更适合于循环变量) )。另外,您不应将大写字母用作私有变量。
2015年

我不同意“相关性”部分。问题清楚地读为:如何在Bash中设置与命令输出相等的变量?我添加了这个答案作为补充,因为我在这里寻找一种解决方案,该解决方案有助于我稍后发布的代码。关于大写var,对此表示感谢。
Gus 2015年

1
这可以写出来,ARR=(3 2 4 1);for((N=3,M=3,COUNT=N-1;COUNT < ${#ARR[@]};ARR[COUNT]*=M,COUNT+=N)){ :;}但我同意@tripleee:我不知道该怎么做,在那里!
F. Hauri

@ F.Hauri ... bash越深入,bash就越像perl!
roblogic

4

还有两种方法:

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

  1. 以下内容分配harshilL然后打印

    L=$"harshil"
    echo "$L"
  2. 以下将命令的输出分配tr给L2。tr正在对另一个变量L1进行运算。

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

4
1. $"..."可能没有按照你的想法去做。2.这已经在安迪·莱斯特的答案中给出。
gniourf_gniourf

@gniourf_gniourf是正确的:请参阅bash本地化不适用于多行。但是在bash下,您可以使用echo ${L1,,}小写或echo ${L1^^}大写。
F. Hauri
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.