Shell的控制和重定向运算符是什么?


245

我经常在网上看到教程,这些教程将各种命令与不同的符号相连。例如:

command1 |  command2
command1 &  command2
command1 || command2    
command1 && command2

其他人似乎正在将命令连接到文件:

command1  > file1
command1  >> file1

这些是什么东西?他们叫什么?他们在做什么?还有更多吗?


有关此问题的元线程。

Answers:


340

这些被称为shell运算符,是的,它们更多。我将简要概述一下两个主要类(控制操作符重定向操作符)中最常见的类,以及它们相对于bash shell的工作方式。

A.控制员

在shell命令语言中,是执行控制功能的令牌。
它是以下符号之一:

&   &&   (   )   ;   ;;   <newline>   |   ||

|&以重击。

一个!没有控制操作员,但一个保留字。它成为算术表达式内部和测试构造内部的逻辑NOT [求反运算符] (尽管仍然需要空格分隔符)。

A.1列表终止符

  • ; :不管另一个命令的结果如何,都将在另一个命令完成后再运行一个命令。

    command1 ; command2

    首先command1运行,在前台运行,一旦完成,command2将运行。

    不在字符串文字中或某些关键字之后的换行符等于分号运算符。列表;分隔简单的命令仍然是一个清单 -在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

    注意,&&||是左关联的;请参阅外壳程序逻辑运算符&&,||的优先级。欲获得更多信息。

  • !:这是一个保留字,用作“非”运算符(但必须具有定界符),用于取消命令的返回状态-如果命令返回非零状态,则返回0;如果返回状态0,则返回1 。对于该test实用程序也是一个逻辑非。

    ! 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

    如果文件不存在,将创建它。

    很少使用该运算符,因为命令通常仅从其stdin中读取,尽管在某些特定情况下它可能会派上用场

  • > :将命令的输出定向到文件中。

    command > out.txt

    上面将保存输出commandout.txt。如果文件存在,则其内容将被覆盖;如果文件不存在,则将创建它。

    此运算符还经常用于选择是将某些内容打印到标准错误还是标准输出中

    command >out.txt 2>error.txt

    在上面的示例中,>将重定向标准输出并2>重定向标准错误。也可以使用重定向输出,1>但是由于这是默认设置,因此1通常会省略并将其简单地编写为>

    因此,运行commandfile.txt并保存其输出out.txt和任何错误消息在error.txt你可以运行:

    command < file.txt > out.txt 2> error.txt
  • >|:与相同>,但是即使外壳已配置为拒绝覆盖(使用set -Cset -o noclobber),也将覆盖目标。

    command >| out.txt

    如果out.txt存在,则输出command将替换其内容。如果不存在,将创建它。

  • >>:与相同>,但如果目标文件存在,则附加新数据。

    command >> out.txt

    如果out.txt存在,则command其中的任何内容之后,的输出都将附加到它的后面。如果不存在,将创建它。

  • &>>&>>&&>>:(非标准)。重定向标准错误和标准输出,分别替换或附加。

    command &> out.txt

    标准错误和的标准输出都command将保存在中out.txt,覆盖其内容,或者如果不存在则创建它。

    command &>> out.txt

    如上所述,除了如果out.txt存在,输出和错误command将被附加到它。

    &>变种起源于bash,而>&变异来自CSH(几十年前)。它们都与其他POSIX Shell运算符冲突,因此不应在可移植sh脚本中使用。

  • <<:此处文档。它通常用于打印多行字符串。

     command << WORD
         Text
     WORD

    在这里,command直到找到下一个出现将采取一切WORDText在上面的例子中,作为输入。尽管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的最常见的运算符。一些外壳具有自己的一些其他重定向操作符。

Ksh,bash和zsh也具有结构<(…)>(…)=(…)zsh仅后者)。这些不是重定向,而是进程替换


2
可能值得一提的是,并非所有shell都相等,并且特别强调了bash特定的功能。
Greg Hewgill 2014年

1
@GregHewgill是的,我通过说我正在就进行讨论而感到疲倦bash。这可以作为规范问答,以解决各种“这奇怪的事情做什么”问题,其中大部分来自bash用户。我希望其他人会介入并回答非bash shell,但是突出显示特定于bash的外壳很有意义。我必须检查一下,但我不知道它们在我头顶上。
terdon

&>,,>>><<<均为非posix,就像在此处文档的名称中引用非唯一的非字母字符一样。这个答案还讨论甚少如何他们的工作-例如,它几乎是有害无益谈论一个简单的命令命令没有解释这些是什么,以及如何壳决定。
mikeserv 2014年

@mikeserv谢谢。他们虽然在bash和zsh上工作。我不知道该列表中真正包含bash的内容(如果有的话)。我应该仔细研究一下,并添加每个可以使用的外壳,但这需要首先找出。
terdon

1
@ Arc676不,它们的取值不能为true或false,这是完全不同的上下文。这仅表示退出值非0表示问题(不是false),退出码0表示成功(不是true)。一直以来都是这样,而且很标准。非0的退出代码表示在我所知道的每个环境中均存在错误。
terdon

60

关于“>”的警告

刚了解I / O重定向(<>)的Unix初学者经常尝试类似的事情

命令input_file > the_same_file

要么

命令 …< 文件      > the_same_file

或几乎相等地

文件 | 命令 ...> the_same_file

grepsedcutsort,和spell是人们都禁不住在这样的结构可以使用的命令的例子。)用户都惊讶地发现,这些情况导致文件变空。

bash(1)Redirection部分的第一句话中可以找到一个在其他答案中似乎未提及的细微差别:

在执行命令之前,可以 使用由Shell解释的特殊符号来重定向其输入和输出。

前五个词应为粗体,斜体,下划线,放大,闪烁,红色和带有红色三角形的感叹号图标标记,以强调以下事实:在执行命令之前,外壳程序执行了请求的重定向 。还要记住

输出重定向导致文件…打开以进行写入…。如果文件不存在,则创建该文件;如果确实存在,则将其截断为零大小。

  1. 因此,在此示例中:

    sort roster > roster

    在程序开始运行roster之前,shell打开文件进行写入,将其截断(即丢弃其所有内容)sort。自然,什么也做不了来恢复数据。

  2. 可能天真地希望

    tr "[:upper:]" "[:lower:]" < poem > poem

    可能会更好。由于外壳程序处理从左到右的重定向,因此在打开它进行写入(对于标准输出)之前,它会打开poem以进行读取(对于tr的标准输入)。但这没有帮助。即使此操作序列产生两个文件句柄,它们都指向同一文件。当shell打开文件进行读取时,内容仍然存在,但是在执行程序之前它们仍然会被破坏。 

那么,该怎么办呢?

解决方案包括:

  • 检查您正在运行的程序是否具有自己的内部功能,以指定输出的位置。这通常用-o(或--output=)令牌表示。尤其是,

    sort roster -o roster

    大致相当于

    sort roster > roster

    除了,在第一种情况下,sort程序将打开输出文件。而且它足够聪明,读取完所有输入文件之后才打开输出文件。

    同样地,至少某些版本sed有一个-i(编辑 N将)选项,可用于输出写回到输入文件(再次,之后所有的输入已经被读取)。像编辑ed/ exemacspico,和vi/ vim 允许用户编辑一个文本文件,并保存在原文件中编辑的文本。请注意,ed(至少)可以非交互式使用。

    • vi具有相关功能。如果键入,它将把编辑缓冲区的内容写到,读取输出,然后将其插入缓冲区(替换原始内容)。:%!commandEntercommand
  • 简单但有效:

    命令input_file > temp_file   && mv temp_file  input_file

    这样做的缺点是,如果input_file是链接,它将(可能)被一个单独的文件替换。同样,新文件将归您所有,并具有默认保护。特别是,这冒着使该文件最终在世界范围内可读的风险,即使原始文件不可读input_file

    变化:

    • commandinput_file > temp_file && cp temp_file input_file && rm temp_file
      仍然(可能)使temp_file世界可读。更好的是:
    • cp input_file temp_file && commandtemp_file > input_file && rm temp_file
      这些保留文件的链接状态,所有者和模式(保护),可能以两倍于I / O的代价为代价。(您可能需要使用类似-a-pon 的选项cp 来告知它保留属性。)
    • commandinput_file > temp_file &&
      cp --attributes-only --preserve=all input_file temp_file &&
      mv temp_file input_file
      (为了便于阅读,将其分成几行)这会保留文件的模式(如果是root用户,则是所有者),但使文件归您所有(如果您不是root用户),并使其成为新文件,单独的文件。
  • 该博客 (文件的“就地”编辑)建议和解释

    {rm input_file   &&   命令 …> input_file ; } < input_file

    这要求command能够处理标准输入(但几乎所有过滤器都可以)。该博客本身将其称为危险的信息,因此不鼓励使用。这还将创建一个新的单独文件(未链接到任何文件),该文件由您拥有并具有默认权限。

  • moreutils软件包具有一个名为的命令sponge

    命令input_file | 海绵the_same_file

    有关更多信息,请参见此答案

这让我感到完全惊讶: 语法错误说

[这些解决方案中的大多数]在只读文件系统上将失败,其中“只读”表示您$HOME 是可写的,但/tmp将是只读的(默认情况下)。例如,如果您具有Ubuntu,并且已启动到故障恢复控制台,通常就是这种情况。同样,here-document运算符<<<也不会在那里工作,因为它需要/tmp读/写, 因为它也会在其中写入一个临时文件。
(参见此问题包括strace'd输出)

在这种情况下,以下方法可能会起作用:

  • 对于高级用户: 如果你的命令,保证生成的输出相同的数据量有输入(例如sort,或者tr 没有-d-s选项),你可以试试
    命令input_file | dd of = the_same_file conv = notrunc
    有关更多信息,请参见此答案此答案,包括对上述内容的解释,以及在保证命令产生与输入相同或更少的输出数据量(例如grep,或cut)的情况下有效的替代方法。这些答案的优点是它们不需要任何可用空间(或者只需要很少的空间)。表格上方的答案 明确要求,系统要有足够的可用空间,才能同时容纳整个输入(旧)文件和输出(新)文件;对于大多数其他解决方案(例如和),这显然也不是正确的。例外:可能需要大量可用空间,因为commandinput_file > temp_file && …sed -ispongesort … | dd …sort 需要先读取其所有输入,然后才能写入任何输出,并且它可能会将大部分(如果不是全部)数据缓存在一个临时文件中。
  • 仅适用于高级用户:
    命令input_file 1 <> the_same_file
    可能等同于dd上面的答案。在语法上打开文件描述符命名文件的输入和输出,但不截断它-排序的组合 和。注意:有些程序(例如和)可能拒绝在这种情况下运行,因为它们可以检测到输入和输出是同一文件。请参阅此答案 ,以进行上述讨论,以及一个脚本,如果保证您的命令产生的输出数据量与input相同或更少,则可以使该答案起作用。 警告:我尚未测试过Peter的脚本,所以我不作保证。n<> filen n<n>catgrep

那么,问题是什么呢?

这已成为U&L的热门话题。在以下问题中得到解决:

……这还不包括超级用户或Ask Ubuntu。在此答案中,我已经结合了上述问题的答案中的很多信息,但不是全部。(即,有关更多信息,请阅读上面列出的问题及其答案。)

PS:我与上面引用的博客没有任何隶属关系。


由于这个问题不断出现,我想我会尽力写一个“规范的答案”。我应该在这里发布(或者从其他一些流量较大的问题链接到它),还是应该将其移至实际上引发此问题的问题之一?另外,这是否可能是合并问题的情况?
斯科特,

/ tmp 一个目录,可用于需要放置临时文件的位置的应用程序。应允许应用程序在此目录中创建文件,但不得假定此类文件在应用程序调用之间被保留。
mikeserv

@mikeserv:是的,(1)我引用的是语法错误,(2)我说我很惊讶。我认为,如果可以读写的话,它将是/tmp
斯科特,

那么,事情@syntaxerror说是加倍奇怪,因为,因为我认为,dash将是在Ubuntu默认的恢复外壳和它不仅不理解<<<herestring,但它也得到匿名管道进行<<Here文档和不惹${TMPDIR:-/tmp}为目的。请参见上这里,文件处理演示。同样为什么输出相同或更少的警告?
mikeserv

@mikeserv:dd … conv=notrunc和,1<>答案永远都不会截断输出文件,因此,如果命令的输出小于输入(例如grep),则文件末尾会剩下一些原始字节。而且,如果输出大于输入较大(如cat -nnl或(潜在的)grep -n),还有就是你读它之前覆盖旧数据的风险。
斯科特

29

更多的观察值;&()

  • 请注意,terdon答案中的某些命令可能为空。例如,你可以说

    command1 ;

    (无command2)。这相当于

    command1

    (即,它只是command1在前台运行并等待其完成。相比之下,

    command1 &

    (不带command2)将command1在后台启动,然后立即发出另一个shell提示。

  • 相比之下,command1 &&command1 ||,并command1 |没有任何意义。如果键入其中一个,则外壳程序将(可能)假定命令继续到另一行。它将显示辅助(继续)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所说,(并且)可以用来对命令进行分组。关于它们与该讨论“无关紧要”的说法值得商bat。Terdon答案中的某些命令可能是命令。例如,

    ( command1 ; command2 )  &&  ( command3; command4 )

    做这个:

    • 运行command1并等待其完成。
    • 然后,无论运行第一个命令的结果如何,都运行command2并等待其完成。
    • 然后,如果command2成功,

      • 运行command3并等待其完成。
      • 然后,无论运行该命令的结果如何,都运行command4并等待其完成。

      如果command2失败,请停止处理命令行。

  • 括号外,|绑定非常紧密,因此

    command1 | command2 || command3

    相当于

    ( command1 | command2 )  ||  command3

    &&||结合除更紧;,因此

    command1 && command2 ; command3

    相当于

    ( command1 && command2 ) ;  command3

    即,command3无论command1和和或的退出状态如何,都将执行command2


完美,+ 1!我说过它们不相关,因为我不想涉及太多细节。我想要一个可以作为新手快速备忘单的答案,这些新手想知道各种命令结尾处所有奇怪的花样是什么。我并不是要暗示它们没有用。感谢您添加所有这些内容。
terdon

1
我担心的“临界质量”的问题-如果我们发布的一切是我们可能说的炮弹,我们会拥有我们自己的TL; DR Bash的参考手册的版本。
G-Man

还值得一提的是:与C系列语言不同,;它本身(或在命令之前没有命令)是语法错误,而不是空语句。因此; ;是错误的。(恕我直言,这是新用户的常见陷阱)。另外:;;是一个特殊的分隔符,用于case语句。
muru 2014年

1
@muru:好点,但是让我们概括一下。任何可以命令之间出现的控制操作的:;&&||&,和|,是错误的,如果他们似乎什么也没有之前他们。另外,terdon ;;在回答中(简短地)说。
G-Man

1
@Wildcard:好的,我知道你来自哪里。关键字是“可以”;我只是说我不保证所有shell都将接受此类构造(即YMMV)。显然,在我不了解linebreakPOSIX Shell语法中令牌的用法之前,我曾写过这篇文章。因此,可以肯定地说所有POSIX兼容的外壳都可以接受它们。我坚持不作一般性声明。如果找到足够旧的POSIX之前的外壳,例如实际的Bourne外壳或更旧的外壳,则所有投注均无效。
G-Man
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.