我正在從 Jenkins 運行 shell 腳本,它使用 shebang options 啟動 shell 腳本#!/bin/sh -ex
。
根據Bash Shebang 適合傻瓜嗎?, -x
, “使 shell 列印執行追蹤”,這對於大多數用途都非常有用 - 除了 echo:
echo "Message"
產生輸出
+ echo "Message"
Message
這有點多餘,而且看起來有點奇怪。有沒有辦法保持-x
啟用狀態,但僅輸出
Message
而不是上面的兩行,例如透過在 echo 命令前添加特殊命令字元或重定向輸出?
答案1
當你被鱷魚逼到脖子的時候,很容易忘記我們的目標是排乾沼澤。 ——流行說法
問題是關於echo
,但到目前為止,大多數答案都集中在如何潛入命令set +x
。
{ echo "Message"; } 2> /dev/null
{ …; } 2> /dev/null
(我承認,如果我沒有在先前的答案中看到它,我可能不會想到這一點。)
這有點麻煩,但是,如果您有一組連續的echo
命令,則不需要單獨對每個命令執行此操作:
{
echo "The quick brown fox"
echo "jumps over the lazy dog."
} 2> /dev/null
請注意,當有換行符號時,不需要分號。
您可以使用以下方法減輕打字負擔克諾布的想法
永久開啟/dev/null
非標準檔案描述符(例如,3),然後說2>&3
而不是2> /dev/null
一直打開。
在撰寫本文時,前四個答案需要做一些特殊的事情(並且在大多數情況下很麻煩)
每一個當你做一個echo
.如果你真的想要全部 echo
命令來抑制執行追蹤(為什麼不呢?),您可以在全域範圍內執行此操作,而無需修改大量程式碼。首先,我注意到沒有跟蹤別名:
$ myfunc()
> {
> date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016 0:00:00 AM # Happy Halloween!
$ myfunc
+ myfunc # Note that function call is traced.
+ date
Mon, Oct 31, 2016 0:00:01 AM
$ myalias
+ date # Note that it doesn’t say + myalias
Mon, Oct 31, 2016 0:00:02 AM
(請注意,如果 shebang 是#!/bin/sh
,則以下腳本片段有效,即使/bin/sh
是 bash 的連結。但是,如果 shebang 是#!/bin/bash
,則需要添加一個shopt -s expand_aliases
命令來獲取別名以在腳本中使用。)
所以,對於我的第一個技巧:
alias echo='{ set +x; } 2> /dev/null; builtin echo'
現在,當我們說 時echo "Message"
,我們正在呼叫別名,該別名不會被追蹤。別名關閉追蹤選項,同時抑制set
命令中的追蹤訊息(使用中首先介紹的技術)用戶5071535的回答),然後執行實際的echo
命令。這使我們能夠獲得與 user5071535 的答案類似的效果,而無需編輯程式碼每一個 echo
命令。但是,這會使追蹤模式關閉。我們不能將 a 放入set -x
別名中(或至少不容易),因為別名只允許用字串替換單字;別名字串的任何部分都不能注入到命令中
後參數(例如,"Message"
)。因此,例如,如果腳本包含
date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date
輸出將是
+ date
Mon, Oct 31, 2016 0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016 0:00:04 AM # Note that it doesn’t say + date
因此,在顯示訊息後,您仍然需要重新開啟追蹤選項,但每次只需要一次堵塞連續echo
命令數:
date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date
那就太好了如果我們能set -x
在之後製造出自動裝置echo
——我們可以,但需要更多的技巧。但在我介紹這一點之前,請先考慮一下這一點。 OP 從使用#!/bin/sh -ex
shebang 的腳本開始。使用者可以隱式地x
從 shebang 中刪除 並擁有一個正常工作的腳本,而無需執行追蹤。如果我們能夠開發出保留該屬性的解決方案,那就太好了。這裡的前幾個答案未能滿足該屬性,因為它們無條件地「返回」追蹤echo
語句,而不考慮它是否已經打開。
這個答案顯然沒有認識到這個問題,因為取代 echo
帶有追蹤輸出的輸出;因此,如果關閉跟踪,所有訊息都會消失。我現在將提出一個解決方案,在echo
聲明後重新打開跟踪有條件的- 僅當它已經打開時。將其降級為無條件「返回」追蹤的解決方案是微不足道的,可以作為練習。
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
builtin echo "$*"
case "$save_flags" in
(*x*) set -x
esac
}
$-
是選項清單;與設定的所有選項相對應的字母串聯。例如,如果設定了e
和選項,那麼將是包含和 的一堆字母。我的新別名(上面)保存了關閉追蹤之前的值。然後,在關閉追蹤的情況下,它將控制權交給 shell 函數。函數執行實際操作 ,然後檢查呼叫別名時是否開啟了該選項。如果該選項已打開,該函數會將其重新開啟;如果它關閉了,該功能就會將其關閉。x
$-
e
x
$-
echo
x
shopt
您可以在腳本開頭插入以上七行(如果包含 則為八行),而保留其餘部分。
這會讓你
- 使用以下任 shebang 行:
#!/bin/sh -ex #!/bin/sh -e #!/bin/sh –x
或者只是簡單的#!/bin/sh
它應該按預期工作。 - 有這樣的程式碼
(舍邦) 命令1 命令2 命令3 設定-x 命令4 命令5 命令6 設定+x 命令7 命令8 命令9
和- 命令 4、5 和 6 將被追蹤 — 除非其中之一是
echo
,在這種情況下它將被執行但不被追蹤。 (但即使命令 5 是一個echo
,命令 6 仍然會被追蹤。) - 命令 7、8 和 9 將不會被追蹤。即使命令 8 是
echo
,命令 9 仍然不會被追蹤。 - 命令 1、2 和 3 將被追蹤(如 4、5 和 6)或不被追蹤(如 7、8 和 9),取決於 shebang 是否包含
x
.
- 命令 4、5 和 6 將被追蹤 — 除非其中之一是
PS我發現,在我的系統上,我可以省略builtin
中間答案中的關鍵字(只是 的別名echo
)。這並不奇怪; bash(1) 表示,在別名擴展期間,...
…。與正在擴展的別名相同的單字不會再次擴展。這意味著人們可以別名
ls
到ls -F
例如,bash 不會嘗試遞歸地擴展替換文字。
毫不奇怪,如果省略關鍵字1echo_and_restore
,最後一個答案(有 的答案)就會失敗。但是,奇怪的是,如果我刪除並切換順序,它會起作用:builtin
builtin
echo_and_restore() {
echo "$*"
case "$save_flags" in
(*x*) set -x
esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
__________
1 它似乎會引起未定義的行為。我見過
- 無限循環(可能是因為無界遞歸),
- 錯誤
/dev/null: Bad address
訊息,以及 - 核心轉儲。
答案2
我在以下位置找到了部分解決方案資訊科技:
#!/bin/bash -ex
set +x;
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"
輸出
set +x;
shell tracing is disabled here
+ echo "but is enabled here"
but is enabled here
不幸的是,那聲音仍然在迴響set +x
,但至少在那之後很安靜。所以這至少是問題的部分解決方案。
但是否有更好的方法來做到這一點? :)
答案3
這種方法透過消除輸出來改進您自己的解決方案set +x
:
#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"
答案4
放在set +x
括號內,因此它僅適用於本地範圍。
例如:
#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true
會輸出:
$ ./foo.sh
+ exec
foo1
foo2
foo3
+ true