$ 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
理解差異。
但是,如果您還包含您自己的和/或專有的軟體,則很難為您提供通用字串。您的某個專有指令有可能(儘管不太可能)使用這樣的字串。您應該查看錯誤訊息的一般設定並建立一個不太可能使用的字串。
如果如問題所示,commandmayfail
是file
,則使用不帶 的字串可能就足夠了:
。
答案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
,該工具可能
- 完全忽略不完整的行,或者
- 處理它並添加缺少的換行符,或者
- 按原樣處理它。
實際上你會
- 錯過輸出的某些部分,或者
- 不知道該行不完整,或者
- 取得附加了退出狀態的行。
如果是 (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
。線路成對出現。 (請參閱下面的範例以更直觀地理解該協定。)
- 讀取一對 ( ) 中的第一行
getline<f
。 - 將第一行儲存在變數 (
first=$0
) 中。 - 讀取一對 ( ) 中的第二行
getline<f
。 - 分析第一行(
$first
)。- 如果是,
C
那麼第二個(當前$0
)是來自 的完整行cmdmayfail
,您可以解析它。 - 如果是,
I
那麼第二個是來自 的不完整行cmdmayfail
,您可能想也可能不想解析它。期待E
下一對。 - 如果是,
E
則第二個是 的退出狀態cmdmayfail
。你不應該期待更多的配對。
- 如果是,
- 環形。
注意我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