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

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

我经常在网上看到教程,用不同的符号连接各种命令。例如:

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

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

command1  > file1
command1  >> file1

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


关于这个问题的元线程。

答案1

这些被称为 shell 操作符,是的,它们还有更多。我将简要概述两个主要类别中最常见的类别,控制操作员重定向操作符,以及它们如何在 bash shell 中工作。

A. 控制操作符

POSIX 定义

在 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也是实用程序的逻辑 NOT 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. 重定向运算符

重定向运算符的 POSIX 定义

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

<     >     >|     <<     >>     <&     >&     <<-     <>

这些允许您控制命令的输入和输出。它们可以出现在简单命令中的任何位置,也可以出现在命令之后。重定向按照它们出现的顺序从左到右进行处理。

  • <:输入命令。

      command < file.txt
    

以上将command在 的内容上执行file.txt

  • <>:与上面相同,但文件打开于读+写模式而不是只读:

      command <> file.txt
    

如果该文件不存在,则会创建该文件。

该运算符很少使用,因为命令通常只不过,从他们的标准输入它可以在许多特定情况下派上用场

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

      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
  
  • >|: 与 相同>,但会覆盖目标,即使 shell 已配置为拒绝覆盖(使用set -Cset -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将采取一切,直到找到下一次出现的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 的最常见操作符。某些 shell 有一些自己的附加重定向运算符。

Ksh、bash 和 zsh 也有结构<(…),>(…)=(…)(仅后一个zsh)。这些不是重定向,而是流程替代

答案2

关于“>”的警告

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

命令……输入文件>同一个文件

或者

命令…<文件     >同一个文件

或者,几乎等价地,

文件|命令…… >同一个文件

grepsedcutsortspell是人们试图在此类结构中使用的命令示例。)用户惊讶地发现这些情况会导致文件变空。

在其他答案中似乎没有提到的细微差别可以在第一句话中找到重定向的部分重击(1):

在执行命令之前,其输入和输出可能是重定向 使用 shell 解释的特殊符号。

前五个单词应为粗体、斜体、下划线、放大、闪烁、红色并标有图标红色三角形中的感叹号,以强调 shell 执行请求的重定向这一事实 在命令执行之前。还要记住

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

  1. 所以,在这个例子中:

    sort roster > roster
    

    shell 打开文件进行写入,在程序开始运行roster之前截断它(即丢弃其所有内容) 。sort当然,无法采取任何措施来恢复数据。

  2. 人们可能会天真地期望

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

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

那么,该怎么办呢?

解决方案包括:

  • 检查您正在运行的程序是否具有自己的内部功能来指定输出的去向。这通常由-o(或) 标记表示--output=。尤其,

    sort -o roster roster
    

    大致相当于

    sort roster > roster
    

    除了在第一种情况下,sort程序打开输出文件。它足够聪明,直到它已读取所有输入文件。

    同样,至少某些版本sed-i(编辑n place) 选项,可用于将输出写回输入文件(同样,所有输入均已读取)。ed/ exemacspicovi/等编辑器vim 允许用户编辑文本文件并将编辑后的文本保存在原始文件中。请注意,ed(至少)可以非交互地使用。

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

    命令……输入文件>临时文件  && MV临时文件 输入文件

    这有一个缺点,如果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),并使其成为一个新的,单独的文件。
  • 这个博客 (文件的“就地”编辑)建议和解释

    { R M输入文件  &&  命令…… >输入文件; } <输入文件

    这要求command能够处理标准输入(但几乎所有过滤器都可以)。该博客本身称这是一种有风险的拼凑行为,并不鼓励其使用。这还将创建一个新的、单独的文件(不链接到任何内容),该文件归您所有并具有默认权限。

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

    命令……输入文件|海绵同一个文件

    这个答案了解更多信息。

以下是令我完全惊讶的事情: 语法错误说:

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

在这种情况下,以下方法可能有效:

  • 仅适用于高级用户: 如果您的命令保证产生与输入相同数量的输出数据(例如,sorttr 没有-d或选项-s),你可以尝试
    命令……输入文件| dd =同一个文件转换=notrunc
    这个答案这个答案了解更多信息,包括对上述内容的解释,以及保证您的命令产生与输入相同数量的输出数据时可行的替代方案或更少(例如,grep, 或cut)。这些答案的优点是它们不需要任何可用空间(或者它们需要很少的空间)。上面表格的答案 明确要求系统有足够的可用空间,以便能够同时容纳整个输入(旧)文件和输出(新)文件;对于大多数其他解决方案(例如,和)来说,这显然也不是正确的。例外:可能需要大量可用空间,因为在写入任何输出之前需要读取其所有输入,并且它可能会在临时文件中缓冲大部分(如果不是全部)数据。commandinput_file > temp_file && …sed -ispongesort … | dd …sort
  • 仅适用于高级用户:
    命令……输入文件1<>同一个文件
    可能相当于dd上面的答案。该语法打开文件描述符上的指定文件n<> filen 对于输入和输出 ,而不截断它——有点像和的组合。注意:某些程序(例如,和)可能会拒绝在这种情况下运行,因为它们可以检测到输入和输出是同一个文件。看n<n>catgrep这个答案 对于上述内容的讨论,以及一个脚本,如果您的命令保证产生与输入相同数量的输出数据,则使该答案起作用或更少
    警告:我还没有测试过彼得的脚本,所以我不保证它。

那么,问题是什么?

这是 U&L 上的热门话题;它通过以下问题得到解决:

…这还不包括超级用户或 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/orcommand3的退出状态如何,都将被执行。command1command2

相关内容