我经常在网上看到教程,用不同的符号连接各种命令。例如:
command1 | command2
command1 & command2
command1 || command2
command1 && command2
其他人似乎将命令连接到文件:
command1 > file1
command1 >> file1
这些是什么东西?他们叫什么?他们在做什么?还有更多吗?
答案1
这些被称为 shell 操作符,是的,它们还有更多。我将简要概述两个主要类别中最常见的类别,控制操作员和重定向操作符,以及它们如何在 bash shell 中工作。
A. 控制操作符
在 shell 命令语言中,执行控制功能的标记。
它是以下符号之一:
& && ( ) ; ;; <newline> | ||
在|&
bash中。
A!
是不是一个控制操作符但是保留字。里面就变成了逻辑NOT[否定运算符]算术表达式以及内部测试结构(同时仍然需要空格分隔符)。
A.1 列出终止符
;
:将在另一个命令完成后运行一个命令,无论第一个命令的结果如何。command1 ; command2
首先command1
是在前台运行,一旦完成,command2
将运行。
不在字符串文字中或在某些关键字之后的换行符是不是相当于分号运算符。分隔的简单命令列表;
仍然是列表- 就像在 shell 的解析器中一样,在执行之前仍然必须继续读入;
分隔简单命令后面的简单命令,而换行符可以分隔整个命令列表或列表列表。区别很微妙,但很复杂:假设 shell 之前没有读取换行符后的数据的命令,则换行符标记 shell 可以开始评估它已经读入的简单命令的点,而分号则;
可以不是。
&
:这将在后台运行命令,允许您继续在同一 shell 中工作。command1 & command2
这里,command1
在后台启动并command2
立即开始在前台运行,无需等待command1
退出。
之后的换行符command1
是可选的。
A.2 逻辑运算符
&&
:用于构建 AND 列表,它允许您仅在另一个命令成功退出时才运行一个命令。command1 && command2
在这里,command2
将在command1
完成后运行仅有的如果command1
成功(如果其退出代码为 0)。这两个命令都在前台运行。
这个命令也可以写成
if command1
then command2
else false
fi
或者只是if command1; then command2; fi
忽略返回状态。
||
:用于构建 OR 列表,它允许您仅在另一个命令退出失败时才运行一个命令。command1 || command2
此处,command2
仅在失败时才运行command1
(如果返回非 0 的退出状态)。这两个命令都在前台运行。
这个命令也可以写成
if command1
then true
else command2
fi
或者以更短的方式if ! command1; then command2; fi
。
请注意,&&
和||
是左关联的;看shell 逻辑运算符 &&、|| 的优先级了解更多信息。
!
:这是一个保留字,充当“非”运算符(但必须有分隔符),用于否定命令的返回状态 - 如果命令返回非零状态,则返回 0;如果返回状态 0,则返回 1也是实用程序的逻辑 NOTtest
。! command1 [ ! a = a ]
算术表达式中真正的 NOT 运算符:
$ echo $((!0)) $((!23))
1 0
A.3 管道操作符
|
:管道运算符,它将一个命令的输出作为另一个命令的输入传递。从管道运算符构建的命令称为管道。command1 | command2
打印的任何输出
command1
都会作为输入传递给command2
.|&
2>&1 |
:这是bash 和 zsh 中的简写。它将一个命令的标准输出和标准错误作为另一个命令的输入传递。command1 |& command2
A.4 其他列表标点符号
;;
仅用于标记一个的结束案例陈述。 Ksh、bash 和 zsh 还支持;&
跳到下一个案例并;;&
(不在 ATT ksh 中)继续测试后续案例。
(
并)
习惯于组命令并在子 shell 中启动它们。{
以及}
组命令,但不要在子 shell 中启动它们。看这个答案讨论 shell 语法中各种类型的圆括号、中括号和大括号。
B. 重定向运算符
在 shell 命令语言中,执行重定向功能的令牌。它是以下符号之一:
< > >| << >> <& >& <<- <>
这些允许您控制命令的输入和输出。它们可以出现在简单命令中的任何位置,也可以出现在命令之后。重定向按照它们出现的顺序从左到右进行处理。
<
:输入命令。command < file.txt
以上将command
在 的内容上执行file.txt
。
<>
:与上面相同,但文件打开于读+写模式而不是只读:command <> file.txt
如果该文件不存在,则会创建该文件。
该运算符很少使用,因为命令通常只读不过,从他们的标准输入它可以在许多特定情况下派上用场。
>
:将命令的输出定向到文件中。command > out.txt
上面将把输出保存command
为out.txt
.如果该文件存在,则其内容将被覆盖;如果该文件不存在,则将创建该文件。
该运算符也经常用于选择是否应将某些内容打印到标准误或者标准输出:
command >out.txt 2>error.txt
在上面的示例中,>
将重定向标准输出并2>
重定向标准错误。也可以使用重定向输出1>
,但是,由于这是默认设置,因此1
通常会省略 ,并且将其简单地写为>
.
因此,要运行command
并file.txt
保存其输出out.txt
以及任何错误消息,error.txt
您将运行:
command < file.txt > out.txt 2> error.txt
>|
: 与 相同>
,但会覆盖目标,即使 shell 已配置为拒绝覆盖(使用set -C
或set -o noclobber
)。command >| out.txt
如果out.txt
存在,则 的输出command
将替换其内容。如果它不存在,它将被创建。
>>
:与 相同>
,只是如果目标文件存在,则附加新数据。command >> out.txt
如果out.txt
存在,则将的输出command
附加到其中已有的内容之后。如果不存在,则会创建它。
>&
:(根据 POSIX 规范)当被包围时数字(1>&2
) 或-
右侧 (1>&-
) 仅重定向一文件描述符或关闭它 (>&-
)。
A>&
后跟文件描述符编号是重定向文件描述符的可移植方式,也是>&-
关闭文件描述符的可移植方式。
如果此重定向的右侧是文件,请阅读下一个条目。
>&
、&>
和:(>>&
另&>>
请参阅上文)重定向标准错误和标准输出,分别进行替换或附加。command &> out.txt
的标准错误和标准输出都command
将保存在 中out.txt
,覆盖其内容或创建它(如果不存在)。
command &>> out.txt
与上面一样,只是如果out.txt
存在,则 的输出和错误command
将附加到其上。
该&>
变体起源于bash
,而该>&
变体来自 csh (几十年前)。它们都与其他 POSIX shell 操作符冲突,并且不应该在可移植脚本中使用sh
。
<<
:这里的文档。它通常用于打印多行字符串。command << WORD Text WORD
在这里,
command
将采取一切,直到找到下一次出现的WORD
,Text
在上面的示例中,作为输入 。虽然WORD
通常是EoF
或其变体,但它可以是您喜欢的任何字母数字(而不仅仅是)字符串。当 的 任何部分WORD
被引用或转义时,此处文档中的文本将按字面意思处理,并且不会执行扩展(例如对变量)。如果不加引号,变量将被扩展。有关更多详细信息,请参阅bash手册。如果要将 的输出
command << WORD ... WORD
直接通过管道传输到另一个或多个命令中,则必须将管道放在与 相同的行上<< WORD
,不能将其放在终止 WORD 之后或后面的行上。例如:command << WORD | command2 | command3... Text WORD
<<<
:此处字符串,与此处文档类似,但用于单行。这些仅存在于 Unix 端口或 rc(它的起源地)、zsh、ksh、yash 和 bash 的某些实现中。command <<< WORD
给出的任何内容都会WORD
被扩展,并且其值将作为输入传递给command
.这通常用于将变量的内容作为命令的输入传递。例如:
$ foo="bar"
$ sed 's/a/A/' <<< "$foo"
bAr
# as a short-cut for the standard:
$ printf '%s\n' "$foo" | sed 's/a/A/'
bAr
# or
sed 's/a/A/' << EOF
$foo
EOF
其他一些运算符 ( >&-
, x>&y
x<&y
) 可用于关闭或复制文件描述符。有关它们的详细信息,请参阅 shell 手册的相关部分(这里例如 bash)。
这仅涵盖了类 Bourne shell 的最常见操作符。某些 shell 有一些自己的附加重定向运算符。
Ksh、bash 和 zsh 也有结构<(…)
,>(…)
和=(…)
(仅后一个zsh
)。这些不是重定向,而是流程替代。
答案2
关于“>”的警告
刚刚了解 I/O 重定向(<
和>
)的 Unix 初学者经常尝试类似的事情
命令……输入文件>同一个文件
或者
命令…<文件 >同一个文件
或者,几乎等价地,
猫文件|命令…… >同一个文件
(grep
、sed
、cut
、sort
和spell
是人们试图在此类结构中使用的命令示例。)用户惊讶地发现这些情况会导致文件变空。
在其他答案中似乎没有提到的细微差别可以在第一句话中找到重定向的部分重击(1):
在执行命令之前,其输入和输出可能是重定向 使用 shell 解释的特殊符号。
前五个单词应为粗体、斜体、下划线、放大、闪烁、红色并标有图标,以强调 shell 执行请求的重定向这一事实 在命令执行之前。还要记住
输出重定向导致文件……打开以进行写入……。如果文件不存在则创建;如果它确实存在,则会被截断为零大小。
所以,在这个例子中:
sort roster > roster
shell 打开文件进行写入,在程序开始运行
roster
之前截断它(即丢弃其所有内容) 。sort
当然,无法采取任何措施来恢复数据。人们可能会天真地期望
tr "[:upper:]" "[:lower:]" < poem > poem
可能会更好。因为 shell 处理从左到右的重定向,所以它会先打开
poem
以进行读取(用于tr
标准输入),然后再打开它进行写入(用于标准输出)。但这没有帮助。尽管这一系列操作产生两个文件句柄,但它们都指向同一个文件。当 shell 打开文件进行读取时,内容仍然存在,但在程序执行之前它们仍然会被破坏。
那么,该怎么办呢?
解决方案包括:
检查您正在运行的程序是否具有自己的内部功能来指定输出的去向。这通常由
-o
(或) 标记表示--output=
。尤其,sort -o roster roster
大致相当于
sort roster > roster
除了在第一种情况下,
sort
程序打开输出文件。它足够聪明,直到后它已读取所有输入文件。同样,至少某些版本
sed
有-i
(编辑我n place) 选项,可用于将输出写回输入文件(同样,后所有输入均已读取)。ed
/ex
、emacs
、pico
和vi
/等编辑器vim
允许用户编辑文本文件并将编辑后的文本保存在原始文件中。请注意,ed
(至少)可以非交互地使用。vi
有一个相关的功能。如果您键入,它会将编辑缓冲区的内容写入:%!command
Entercommand
,读取输出,并将其插入缓冲区(替换原始内容)。
简单但有效:
命令……输入文件>临时文件 && MV临时文件 输入文件
这有一个缺点,如果
input_file
是一个链接,它将(可能)被一个单独的文件替换。此外,新文件将归您所有,并具有默认保护。特别是,这带来了文件最终被全世界可读的风险,即使原始文件input_file
不是。变化:
command … input_file > temp_file && cp temp_file input_file && rm temp_file
这仍然(可能)留下temp_file
世界可读。更好的是:cp input_file temp_file && command … temp_file > input_file && rm temp_file
这些保留了文件的链接状态、所有者和模式(保护),但可能会付出两倍 I/O 的代价。 (您可能需要使用类似-a
或-p
on 的选项cp
来告诉它保留属性。)command … input_file > temp_file &&
cp --attributes-only --preserve=all input_file temp_file &&
mv temp_file input_file
(为了可读性而分成单独的行)这保留了文件的模式(如果您是 root,则保留文件的所有者),但使其归您所有(如果您不是 root),并使其成为一个新的,单独的文件。
这个博客 (文件的“就地”编辑)建议和解释
{ R M输入文件 && 命令…… >输入文件; } <输入文件
这要求
command
能够处理标准输入(但几乎所有过滤器都可以)。该博客本身称这是一种有风险的拼凑行为,并不鼓励其使用。这还将创建一个新的、单独的文件(不链接到任何内容),该文件归您所有并具有默认权限。moreutils 包有一个名为的命令
sponge
:命令……输入文件|海绵同一个文件
看这个答案了解更多信息。
以下是令我完全惊讶的事情: 语法错误说:
[大多数这些解决方案]将在只读文件系统上失败,其中“只读”意味着您的
$HOME
将要可写,但/tmp
将会只读(默认情况下)。例如,如果您有 Ubuntu,并且已启动到故障恢复控制台,那么这种情况很常见。此外,这里文档操作符<<<
也不会在那里工作,因为它/tmp
需要读/写 因为它也会向其中写入一个临时文件。
(参见这个问题包括strace
'd 输出)
在这种情况下,以下方法可能有效:
- 仅适用于高级用户:
如果您的命令保证产生与输入相同数量的输出数据(例如,
sort
或tr
没有-d
或选项-s
),你可以尝试命令……输入文件| dd =同一个文件转换=notrunc
看这个答案 和这个答案了解更多信息,包括对上述内容的解释,以及保证您的命令产生与输入相同数量的输出数据时可行的替代方案或更少(例如,grep
, 或cut
)。这些答案的优点是它们不需要任何可用空间(或者它们需要很少的空间)。上面表格的答案 明确要求系统有足够的可用空间,以便能够同时容纳整个输入(旧)文件和输出(新)文件;对于大多数其他解决方案(例如,和)来说,这显然也不是正确的。例外:可能需要大量可用空间,因为在写入任何输出之前需要读取其所有输入,并且它可能会在临时文件中缓冲大部分(如果不是全部)数据。command … input_file > temp_file && …
sed -i
sponge
sort … | dd …
sort
- 仅适用于高级用户:
命令……输入文件1<>同一个文件
可能相当于dd
上面的答案。该语法打开文件描述符上的指定文件n<> file
n
对于输入和输出 ,而不截断它——有点像和的组合。注意:某些程序(例如,和)可能会拒绝在这种情况下运行,因为它们可以检测到输入和输出是同一个文件。看n<
n>
cat
grep
这个答案 对于上述内容的讨论,以及一个脚本,如果您的命令保证产生与输入相同数量的输出数据,则使该答案起作用或更少。
警告:我还没有测试过彼得的脚本,所以我不保证它。
那么,问题是什么?
这是 U&L 上的热门话题;它通过以下问题得到解决:
- 有没有办法就地修改文件?
- 如何用
iconv
转换后的输出替换输入文件? - 为什么该命令
shuf file > file
留下一个空文件? - 我可以在 Linux 中读写同一个文件而不覆盖它吗?
- 重定向到与命令处理的源文件相同的文件
- 为什么这个
sort
命令给我一个空文件? - 将
tr
标准输出重定向到文件 - grep:输入文件“X”也是输出
- 重定向运算符是否并行打开文件描述符?
- 重定向不覆盖文件而只是生成一个空白文件
…这还不包括超级用户或 Ask Ubuntu。我在这个答案中纳入了上述问题答案中的很多信息,但不是全部。 (即,如需了解更多信息,请阅读上面列出的问题及其答案。)
PS我有不与我上面引用的博客的隶属关系。
答案3
;
更多关于、&
、(
和 的观察)
请注意,terdon 的答案中的某些命令可能为空。例如,你可以说
command1 ;
(没有
command2
)。这相当于command1
(即,它只是
command1
在前台运行并等待其完成。相比之下,command1 &
(没有)将在后台
command2
启动,然后立即发出另一个 shell 提示符。command1
相比之下,
command1 &&
、command1 ||
、 和command1 |
没有任何意义。如果您键入其中一个,shell(可能)会假设该命令继续到另一行。它将显示辅助(继续)shell 提示符,通常设置为>
,并继续阅读。在 shell 脚本中,它只会读取下一行并将其附加到已读取的内容中。 (请注意:这可能不是您想要发生的情况。)注意:某些 shell 的某些版本可能会将此类不完整的命令视为错误。在这种情况下(或者事实上,在任何
\
如果命令很长),可以在行尾添加反斜杠 ( ) 来告诉 shell 继续读取另一行的命令:command1 && \ command2
或者
find starting-directory -mindepth 3 -maxdepth 5 -iname "*.some_extension" -type f \ -newer some_existing_file -user fred -readable -print
正如 terdon 所说,
(
和)
可用于对命令进行分组。它们与该讨论“不太相关”的说法是有争议的。terdon 的回答中的一些命令可能是命令团体。例如,( command1 ; command2 ) && ( command3; command4 )
做这个:
- 运行
command1
并等待它完成。 - 然后,无论运行第一个命令的结果如何,都运行
command2
并等待它完成。 那么,如果
command2
成功的话,- 运行
command3
并等待它完成。 - 然后,无论运行该命令的结果如何,都运行
command4
并等待它完成。
如果
command2
失败,则停止处理命令行。- 运行
- 运行
在括号外面,
|
结合得非常紧密,所以command1 | command2 || command3
相当于
( command1 | command2 ) || command3
和
&&
比||
结合得更紧;
,所以command1 && command2 ; command3
相当于
( command1 && command2 ) ; command3
即,无论and/or
command3
的退出状态如何,都将被执行。command1
command2