Bash

如何通過管道將命令發送到 grep 並獲取退出狀態,同時仍將命令輸出發送到 stdout 並將錯誤發送到 stderr?

  • January 13, 2022

具體案例:

我正在嘗試使用curl的 URL -v,將詳細的調試資訊正常輸出到 stderr,將響應正文正常輸出到 stdout,但還要檢查響應中是否存在正則表達式,grep如果未找到,則退出非零。該命令的最簡單形式是

curl -v "${url}" | grep -q "${pattern}"

grep消耗響應體。

我試過的:

  1. 我看過“ Convince grep to output all lines, not just those with matches ”,它會建議如下內容:
curl -v "${url}" | grep --color -E "${pattern}|$"

但這只會突出顯示匹配的顏色,如果不存在,它不會為您提供非零退出程式碼pattern。 2. 我還查看了“如何獲取 grep 退出程式碼但列印所有行?”,這表明:

curl -v "${url}" | tee /dev/stderr | grep -q "${pattern}"

它以退出程式碼退出grep並輸出響應正文;但它將響應主體輸出到標準錯誤,我想保持curl標準錯誤和標準輸出流分開且完整。 3. 在“直接輸出到管道和標準輸出”之後,我嘗試了

curl -v "${url}" | tee >(grep -q "${pattern}")

但是雖然這會輸出所有內容,並正確分離 stdout 和 stderr 流,但它會丟棄grep退出程式碼。

目前解決方法:

到目前為止,我唯一能想到的就是將響應正文粘貼在一個臨時文件中:

curl -v "${url}" | tee /tmp/response
grep -q "${pattern}" /tmp/response

這讓我得到了正確的輸出和正確的退出程式碼。

但是肯定有一種方法可以作為單個管道執行此操作,而無需臨時文件?

你的第二個答案是在正確的軌道上。嘗試

{ curl -v "${url}" | tee /dev/fd/3 | grep -q "${pattern}";} 3>&1

簡要說明:

  • 3>&1文件描述符 1(標準輸出)複製到文件描述符 3(沒有標準用法的最低值)。這是指整個管道(即整個命令行)的標準輸出。新文件描述符 (3) 對整個管道有效。

你可以使用任何數字——至少是 9 以內的任何數字 。POSIX Shell 命令語言規範, 第 2.7 節重定向,說

…所有實現應至少支持 0 到 9 …

暗示符合 POSIX 的 shell 可能無法辨識大於 9 的數字。並且 bash(1)

使用大於 9 的文件描述符的重定向應謹慎使用……

  • tee /dev/fd/3告訴tee寫入文件描述符 3——因此它tee連接到管道的標準輸出。

    • 您必須在此處使用與***n***>&1指令中使用的相同的數字。
    • 類似的文件名 可能不適用於非 Linux 作業系統。/dev/fd/*n*請注意, 這 與 的標準輸出無關,標準輸出teegrep.

Stéphane Chazelas指出

grep -q找到模式時退出,如果在此之後寫入輸出將導致tee死亡。tee至少 GNU 實現 -p 可以忽略 SIGPIPE 並繼續寫入仍然可以獲得輸出的目標。還要注意,一些 shell 只等待管道的最後一個組件,所以在這裡,一旦grep -q找到模式就會攜帶腳本的其餘部分……

作為形式,我驗證了第一點(如果模式匹配,管道可以提前終止)1。

我提供了這個增強的解決方案來解決這兩個問題:

{ curl -v "${url}" | tee /dev/fd/3 | { grep -q "${pattern}" && cat > /dev/null;} } 3>&1

筆記:

  • 如果數據流與模式匹配,grep則將讀取整個流(即其輸入),因此不會出現問題。並且,在這種情況下,grep將“失敗”,因此,由於 &&, cat將不會執行,複合命令將從grep;返回退出程式碼。即失敗(即不匹配)。
  • 如果數據流確實與模式匹配,那麼(正如 Stéphane 指出的那樣) grep將在匹配模式時退出,而不是讀取整個輸入(即來自 的輸出curl | tee)。但在這種情況下,grep將“成功”,因此,由於 &&, cat 它將執行,並且它將吸收其餘數據(直到 EOF)。這將確保能夠將整個數據流寫入管道,並且管道(即整個命令行)在處理完所有輸出tee之前不會終止 。curl

這在技術上仍然存在問題:如果數據流與模式匹配並且grep“成功”,那麼管道的退出狀態將是cat. 我不知道為什麼*(pipe)* | cat > /dev/null會失敗,但理論上是可能的。為了防止這種情況,我提供:

{ curl -v "${url}" | tee /dev/fd/3 | if grep -q "${pattern}"; then cat > /dev/null; true; else false; fi; } 3>&1

如果成功則顯式返回 true grep,如果失敗則顯式返回 false。


1-p選項tee是在版本 8.24 (2015-07-03)中添加 的。


對於上述任何變體,如果您想 從其他地方管道輸出,只需在命令行末尾添加管道,就像您通常那樣:curl

{ curl …; fi; } 3>&1 | lpr

但是,如果要將其重定向到文件,則必須在以下內容之前插入 輸出重定向3>&1

{捲曲...; 菲; } **>*輸出文件***3>&1

回想一下,“管道到文件”是不正確的術語。

引用自:https://unix.stackexchange.com/questions/685984