如何将命令的输出分配给 shell 变量?

Nat*_* G. 95 bash io-redirection shell-script variable

我想将表达式的结果(即命令的输出)分配给一个变量,然后对其进行操作——例如,将它与一个字符串连接起来,然后回显它。这是我所拥有的:

#!/bin/bash
cd ~/Desktop;
thefile= ls -t -U | grep -m 1 "Screen Shot";
echo "Most recent screenshot is: "$thefile;
Run Code Online (Sandbox Code Playgroud)

但是输出:

Screen Shot 2011-07-03 at 1.55.43 PM.png
Most recent screenshot is: 
Run Code Online (Sandbox Code Playgroud)

因此,它看起来没有分配给$thefile,并且在执行时正在打印。

我错过了什么?

Gil*_*il' 136

shell 赋值是一个单词,等号后没有空格。所以你写的内容分配了一个空值thefile;此外,由于赋值与命令组合在一起,因此它创建thefile了一个环境变量,并且赋值对于该特定命令是本地的,即只有调用才能ls看到分配的值。

您想要捕获命令的输出,因此您需要使用命令替换

thefile=$(ls -t -U | grep -m 1 "Screen Shot")
Run Code Online (Sandbox Code Playgroud)

(一些文献显示了另一种语法thefile=`ls …` ;反引号语法等效于美元括号语法,只是在反引号内引用有时很奇怪,所以只需使用$(…)。)

关于您的脚本的其他评论:

  • 结合-t(按时间排序)和-U(不要用 GNU 排序ls)没有意义;只需使用-t.

  • grep用于匹配屏幕截图相比,将通配符传递给ls并用于head捕获第一个文件更清晰:

      thefile=$(ls -td -- *"Screen Shot"* | head -n 1)
    
    Run Code Online (Sandbox Code Playgroud)
  • 它通常是一个坏主意来解析的输出ls。如果您的文件名包含不可打印的字符,这可能会非常失败。但是,ls如果没有,就很难按日期对文件进行排序,因此,如果您知道文件名中没有不可打印的字符或反斜杠,那么这是一个可以接受的解决方案。

  • 始终在变量替换周围使用双引号,即在这里写

      echo "Most recent screenshot is: $thefile"
    
    Run Code Online (Sandbox Code Playgroud)

    如果没有双引号,变量的值被重新展开,如果包含空格或其他特殊字符会导致麻烦。

  • 行尾不需要分号。它们是多余但无害的。

  • 在 shell 脚本中,包含set -e. 这告诉 shell 在任何命令失败时退出(通过返回非零状态)。

如果您有 GNUfind并且sort(特别是如果您运行的是非嵌入式 Linux 或 Cygwin),还有另一种方法可以找到最新的文件:find列出文件及其日期,并使用sortread(这里假设bashzsh为了-d ''读取NUL 分隔的记录)以提取最年轻的文件。

IFS=/ read -rd '' ignored thefile < <(
  find -maxdepth 1 -type f -name "*Screen Shot*" -printf "%T@/%p\0" |
    sort -rnz)
Run Code Online (Sandbox Code Playgroud)

如果您愿意在 zsh 而不是 bash 中编写此脚本,则有一种更简单的方法来捕获最新文件,因为 zsh 具有glob 限定符,不仅允许名称上的通配符匹配,还允许文件元数据上的通配符匹配。在(om[1])该模式之后的部分是水珠限定符; om通过增加年龄(即按修改时间,最新的在前)[1]对匹配项进行排序并仅提取第一个匹配项。整个匹配项需要在括号中,因为它在技术上是一个数组,因为 globbing 返回一个文件列表,即使[1]在这种特殊情况下意味着该列表包含(最多)一个文件。

#!/bin/zsh
set -e
cd ~/Desktop
thefile=(*"Screen Shot"*(om[1]))
print -r "Most recent screenshot is: $thefile"
Run Code Online (Sandbox Code Playgroud)

  • 哇!比我可能希望的更多的信息。多次感谢您的回答;**我真的很感谢你为此付出的所有努力!**你刚刚向我展示了我真的,真的有很多东西要学。我要去买一本关于 bash 的书:P。 (10认同)
  • 这就是我所说的*确定的*答案!:) (4认同)

Jah*_*hid 7

如果你想用 multiline/multiple command/s 来做,那么你可以这样做:

output=$( bash <<EOF
#multiline/multiple command/s
EOF
)
Run Code Online (Sandbox Code Playgroud)

或者:

output=$(
#multiline/multiple command/s
)
Run Code Online (Sandbox Code Playgroud)

例子:

#!/bin/bash
output="$( bash <<EOF
echo first
echo second
echo third
EOF
)"
echo "$output"
Run Code Online (Sandbox Code Playgroud)

输出:

first
second
third
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的答案。有没有办法可以将变量指定为命令的一部分?例如 `output=$(echo $someVariable)` (2认同)

Sté*_*las 5

为了完整起见,在不同的 shell 中:

伯恩外壳

70 年代末的 Bourne shell 引入了命令替换。现在它已不再使用,但许多 shell 的语法都基于该 shell 的语法。

var=`any shell code`
Run Code Online (Sandbox Code Playgroud)

在子进程中解释any shell code,然后输出将进入管道。同时,shell 从管道的另一端读取输出并将其存储在$var变量中。

但请注意,该输出中的所有尾随换行符都将被删除。

Bourne shell 不支持数组变量,但您可以使用以下命令将命令输出的单词存储到位置参数($1, $2...)中:

set -- `some code`
Run Code Online (Sandbox Code Playgroud)

或者:

set x `some code`; shift
Run Code Online (Sandbox Code Playgroud)

在早期版本中--不支持。

, 除了尾部换行符剥离之外, 的输出some code也受到$IFS基于 - 的分割 + 通配符的影响。例如,如果某些代码输出foo,/*<newline><newline>$IFS包含,$1则将包含/foo,其余参数是 中的所有非隐藏文件/

西施

同样是从 70 年代末到 80 年代初,当时非常流行,tcsh尽管有许多缺点,但仍然在某些系统上幸存下来。

set array = `some code`
Run Code Online (Sandbox Code Playgroud)

some code将(在空格、制表符或换行符上)的输出分割后的单词存储在$array列表变量中。

set array = "`some code`"
Run Code Online (Sandbox Code Playgroud)

相同,只是仅在换行符上进行分割( 的元素$array将是输出的非空行)。

ksh88 和 POSIX 等 shell。

如今,sh标准 sh 语言的解释器的一个或其他实现,它本身主要基于 ksh88 的子集,ksh88 是 David Korn 编写的 shell 的最后一个演变,衍生自 Bourne shell,并进行了一些修复和增强功能。其中包括 bash、dash、ksh、yash、mksh、zsh(尽管对于其中许多,必须通过某些选项启用对 sh 规范的遵从,或者将它们称为sh)。

var=$(any shell code)
Run Code Online (Sandbox Code Playgroud)

它的工作原理类似于 Bourne shell,var=`...`只不过它使嵌套变得更加容易,并且不会弄乱其中反斜杠的解释。

尽管 ksh 有数组,但 POSIX sh 规范中并未包含数组支持。

rc 和导数

rc是 80 年代末 Research Unix V10 和 plan9 的外壳,曾经是 Unix 的后继者。还存在该 shell 的公共领域克隆,它产生了一些衍生品:至少esakanga这样。它的语法比上面的要好得多,但不幸的是从未真正流行起来。

array = `cmd
Run Code Online (Sandbox Code Playgroud)
array = ` {any shell code}
Run Code Online (Sandbox Code Playgroud)

存储 的输出any shell code,将 的字符拆分$ifs$array

array = `` (chars) {any shell code}
Run Code Online (Sandbox Code Playgroud)

相同,只是chars使用指定的值而不是$ifs进行拆分。使用var = ``(){shell code},可以准确地存储输出,无需拆分(唯一可以真正开箱即用地执行此操作的 shell)。

克什

ksh 是 80 年代初第一个引入数组的类 Bourne shell。语法是:

set -A array -- $(some code)
Run Code Online (Sandbox Code Playgroud)

与 Bourne shell 一样,它执行尾随换行符剥离、$IFS-分割和通配符操作。

some command | read var1 var2 var3
Run Code Online (Sandbox Code Playgroud)

还可以用于读取 输出的第一行some command,将其拆分$IFS(但可以使用反斜杠来转义分隔符和换行符)并将结果存储在这些变量中。

它也适用于 zsh,但不适用于其他一些类似 ksh 的 shell,例如 bash、yash 或 pdksh/mksh,它们read在子 shell 中运行(对于sh,标准未指定它是否发生,因此您不能在 ) 中可移植地使用它sh。在 bash 中,shopt -s lastpipe有助于 shell 的非交互式调用。

some code | read -A array
Run Code Online (Sandbox Code Playgroud)

相同,除了第一行的单词存储在数组元素中(bash将选项重命名-a为其自己的read内置选项)。

ksh 还引入了协同进程。

some command |&
IFS= read -r first_line <&p
IFS= read -r second_line <&p
Run Code Online (Sandbox Code Playgroud)

例如,可用于将输出中的第一行和第二行读取some command到各自的变量中。

zsh 和 bash 后来还使用不同的语法添加了协同进程支持。

桀骜

zsh(另一个类似 Bourne 的 shell,但也具有 csh 和 rc 的功能)在 1991 年的 2.0 版本中引入了数组支持(前一年发布第一个版本几个月后)。

array=( $(some code) )
Run Code Online (Sandbox Code Playgroud)

将执行换行符剥离和 IFS 分割(不是通配符)并将结果字分配给数组的元素。

该语法后来被 1993 年的 ksh93 和 1996 年的 bash 2.0 复制,尽管它们像 ksh88 一样在顶部进行通配。

$IFS与in以外的事物分开zsh

array=( ${(f)"$(some code)"} ) # split on lineFeed aka newline
array=( ${(0)"$(some code)"} ) # split on NUL
array=( ${(s[string])"$(some code)"} ) # split on any string
Run Code Online (Sandbox Code Playgroud)

zsh也是唯一可以在其变量中存储 NUL 字符的 shell,因此是唯一不会因包含此类字符的命令的输出而阻塞的 shell。NUL 实际上是$IFSin的默认值zsh(除了空格、制表符和换行符之外,就像其他类似 Bourne 的 shell 中一样)。

some command | IFS= read -rd '' var
Run Code Online (Sandbox Code Playgroud)

可用于将some command最多第一个 NUL 字符的输出存储到 中$var。由于 NUL 不能出现在文本中,因此这是一种按原样存储文本的方法。-d来自 ksh93,但-d ''对 NUL 进行定界是 bash/zsh 的补充。

zsh 还添加了一些更多选项,read例如读取任意数量的字符(最初旨在读取按键,但与指定要读取的 fd-k结合使用时可用于任意字符)。后来添加了/具有相关语义的选项:-uksh93bash-N-n

some command | read -u0 -k10 var
Run Code Online (Sandbox Code Playgroud)

some command存储in输出的前 10 个字符$var

sysread模块中的构建还可zsh/system用于读取块中的输入,作为read()系统调用的原始接口。

同名模块中的内置函数zpty也可用于通过伪终端对与命令进行交互,从而允许您以更具交互性的方式发送输入并读取其输出expect

克什93

由其作者于 1993 年末发布的对 ksh 的完全重写,它极大地启发了 bash 2+(以及在某种程度上 zsh 和 mksh)

var=${
  any shell code
}
Run Code Online (Sandbox Code Playgroud)

var=$(any shell code)这与except不在子 shell 环境中运行相同any shell code,这意味着它的效率稍高一些,因为 shell 不需要相同并在之后恢复环境,并且意味着您在其中所做的修改之后仍然存在。

最新版本的mksh.

巴什

readarray array < <(some command)
Run Code Online (Sandbox Code Playgroud)

将行some command(包括可以使用选项删除的分隔符-t)存储在数组的元素中$array<(...)所谓的进程替换在 80 年代中期被添加到 ksh 中,但最初不能用作重定向的目标(很久以后在 ksh93 中解决了)。

在 bash 4.4+ 中,您可以使用除换行符之外的不同分隔符,包括 NUL:

readarray -t -d '' array < <(find . -print0)
Run Code Online (Sandbox Code Playgroud)

find例如,可用于将 的输出中以 NUL 分隔的记录的内容存储到$array.

您还可以这样做:

read var1 var2 < <(some command)
Run Code Online (Sandbox Code Playgroud)

即使lastpipe未启用该选项,它也可以工作(在 zsh 中也可以工作)。

亚什

read var1 var2 <(any shell code)
Run Code Online (Sandbox Code Playgroud)

虽然看起来像 ksh 的进程替换,但其中的功能称为进程重定向,并且不会扩展到某个命名管道的路径,而是将相应的 fd(此处为 0)分配给另一端连接到的管道的读取端命令的输出。

相对较晚的出现者,其语法与其他 shell 显着不同(通常要好得多,尽管目标仍然在变化):

set array (some shell code)
Run Code Online (Sandbox Code Playgroud)

将 shell 代码输出的每一行内容分配给数组的元素。

some command | read var1 var2
Run Code Online (Sandbox Code Playgroud)

将读取第一行,分割$IFS(尽管会改变)或任意分隔符并-d delim存储在$var1,中$var2read -a array将该行的单词存储在数组中。

some command | read -L var1 var2
Run Code Online (Sandbox Code Playgroud)

对于不同变量中的前两行。

some command | read -z var
Run Code Online (Sandbox Code Playgroud)

对于第一个 NUL 之前的所有内容(因此文本输出的所有内容)都存储在$var.