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 所說,(and)可用於將指令分組。關於它們與該討論「並不真正相關」的說法值得商榷。 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

相關內容