控制未命名管道中錯誤的序列

控制未命名管道中錯誤的序列
$ awk -v f=<(cmdmayfail) -e 'BEGIN { r = getline < f ; print r }'
-bash: cmdmayfail: command not found
0

在上面的未命名管道範例中,awk不會從未命名管道中得知錯誤。

$ awk -v f=<(cmdmayfail || echo "control sequence" ) -e 'BEGIN { r = getline < f ; print r }'

為了awk意識到這個錯誤,我可以透過發送控制序列和一些錯誤訊息來使用上面的程式碼。

鑑於file已經知道許多文件類型,是否有合理的控制序列可用於此應用程序,以便awk不會將錯誤控制序列誤認為來自合法文件?謝謝。

答案1

如果您的commandmayfail命令是標準 Unix 命令,則在控制序列之前加上您自己的複雜文字(例如__ERROR__CMDFAIL__:)應該足以讓您awk理解差異。

但是,如果您還包含您自己的和/或專有的軟體,則很難為您提供通用字串。您的某個專有指令有可能(儘管不太可能)使用這樣的字串。您應該查看錯誤訊息的一般設定並建立一個不太可能使用的字串。

如果如問題所示,commandmayfailfile,則使用不帶 的字串可能就足夠了:

答案2

如果輸出cmdmayfail相對較小,且命令本身獨立於程式碼的其他部分終止,那麼您可以將其輸出儲存在變數中,並將退出狀態作為第一行傳遞。中的程式碼<()如下:

out="$(cmdmayfail)"; printf '%s\n' "$?" "$out"

awk應該getline<f獲得退出狀態。連續getline<f將讀取 的實際輸出cmdmayfail

限制:

  • Bash 中的變數無法儲存空字元。
  • $()將刪除所有尾隨換行符;然後printf將恰好添加 1。避免這種情況的一個麻煩技巧是:

    out="$(cmdmayfail; s="$?"; printf X; exit "$s")"; printf '%s\n%s' "$?" "${out%X}"
    

cmdmayfail您處理區塊中的輸出的事實BEGIN可能表明您希望cmdmayfail提前終止。也許這個解決方案就夠了。


一般來說,cmdmayfail甚至可能「無休止地」運行(即直到您終止它),並且您可能希望在處理awk.在這種情況下,上述解決方案將無法運作。

您可以在輸出的每一行前面新增cmdmayfail一些固定狀態行(例如OK),最後新增退出狀態為 的行cmdmayfail。中的程式碼<()如下:

 cmdmayfail | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

例子:

$ (printf '%s\n' foo "bar baz"; exit 7) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"
OK
foo
OK
bar baz
7

然後你的awk程式碼應該getline<f檢查它是否是OK.如果是這樣,下一行(再次)肯定getline<f來自。cmdmayfail循環解析所有行,直到沒有OK您期望的行。然後就是退出狀態。

這將工作正常,除非cmdmayfail可能產生不完整的線。例子:

$ (printf 'foo\nincomplete line'; exit 22) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

根據 的實現sed,該工具可能

  1. 完全忽略不完整的行,或者
  2. 處理它並添加缺少的換行符,或者
  3. 按原樣處理它。

實際上你會

  1. 錯過輸出的某些部分,或者
  2. 不知道該行不完整,或者
  3. 取得附加了退出狀態的行。

如果是 (3),printf '\n%s\n' "${PIPESTATUS[0]}"可能會有所幫助。如果最後一行cmdmayfail完整,它將產生一個額外的空白行;這樣你的awk程式碼就能知道。

cmdmayfail考慮中線被強行終止的情況。那麼您可能不想解析不完整的行。問題是:要知道awk行形式是否cmdmayfail完整,您需要測試下一個(狀態)行。為此實現有用的邏輯awk可能至少是不方便的。


盡快偵測出不完整的線路是有好處的,read在 Bash 中可以做到這一點。缺點是read速度慢(記住 Bash 變數不能儲存空字元)。解決方案範例:

# define this helper function in the main shell
encerr () { ( eval "$@" ) | (while IFS= read -r line; do printf 'C\n%s\n' "$line"; done; [ -n "$line" ] && printf 'I\n%s\n' "$line") ; printf 'E\n%s\n' "${PIPESTATUS[0]}"; }
# this part you want to put in <()
encerr cmdmayfail

然後你需要解碼裡面的自訂協定awk。線路成對出現。 (請參閱下面的範例以更直觀地理解該協定。)

  1. 讀取一對 ( ) 中的第一行getline<f
  2. 將第一行儲存在變數 ( first=$0) 中。
  3. 讀取一對 ( ) 中的第二行getline<f
  4. 分析第一行( $first)。
    • 如果是,C那麼第二個(當前$0)是來自 的完整行cmdmayfail,您可以解析它。
    • 如果是,I那麼第二個是來自 的不完整行cmdmayfail,您可能想也可能不想解析它。期待E下一對。
    • 如果是,E則第二個是 的退出狀態cmdmayfail。你不應該期待更多的配對。
  5. 環形。

注意我eval "$@"在函數內部使用了。你之後寫的內容encerr將被第二次評估,所以通常你會想運行類似的東西

encerr 'cmd1 -opt foo'

或者

encerr "cmd1 -opt foo"

甚至

encerr 'cmd1 -opt foo | cmd2'

基本上,這是您用來運行遠端命令的形式ssh。比較:

ssh a@b 'cmd1 -opt foo | cmd2'

或者您可以像這樣建立函數:

encerr () { "$@" | …

並這樣稱呼它:

encerr cmd1 -opt foo

相較於sudo

sudo cmd1 -opt foo

範例(使用原始函數eval):

  • 成功但輸出為空

    $ encerr true
    E
    0
    
  • 失敗且輸出為空

    $ # I'm redirecting stderr so "command not found" doesn't obfuscate the example
    $ encerr nonexisting-command-foo1231234 2>/dev/null
    E
    127
    
  • 完成線路後成功

    $ encerr 'date; sleep 1; date'
    C
    Mon Sep 30 09:07:40 CEST 2019
    C
    Mon Sep 30 09:07:41 CEST 2019
    E
    0
    
  • 完成線路後失敗

    $ encerr 'printf "foo\nbar\n"; false'
    C
    foo
    C
    bar
    E
    1
    
  • 一條不完整的線後成功

    $ encerr 'printf "foo bar\n89 baz"'
    C
    foo bar
    I
    89 baz
    E
    0
    
  • 線路不完整後失敗

    $ encerr 'printf "\nThe first line was empty and this one was interru"; exit 33'
    C
    
    I
    The first line was empty and this one was interru
    E
    33
    

相關內容