Bash

跟隨重定向時未按預期執行 $-expansions

  • June 1, 2019

似乎bash並且zsh將在子程序中執行變數和算術擴展

a) 他們遵循重定向運算符,如<,或.>``>>``<<<

b)他們所屬的命令不是內置或函式。

bash -c 'i=0; /bin/echo > $((i=7)).txt; echo $i'
0
zsh -c 'i=0; /bin/echo > $((i=7)).txt; echo $i'
0

ksh -c 'i=0; /bin/echo > $((i=7)).txt; echo $i'
7

ksh上面就像除bashor之外的任何其他 shell zsh

這與算術擴展無關:類似地,同樣的事情發生在

unset i; /bin/echo >${i:=7}.txt; echo $i

只會在or7以外的 shell 中列印。bash``zsh

然而,好像這還不夠糟糕,行為在bash和之間以任何可理解的方式不一致zsh

bash -c 'i=0; command echo > $((i++)).txt; echo $i'
1
zsh -c 'i=0; command echo > $((i++)).txt; echo $i'
0

bash -c 'i=0; i=$i /usr/bin/printenv i > $((++i)).bash; echo $i; cat *.bash'
0
0
zsh -c 'i=0; i=$i /usr/bin/printenv i > $((++i)).zsh; echo $i; cat *.zsh'
0
1

所以,我的問題是:**標準是怎麼說的?**這可以接受嗎?

我能夠找到很多關於變數賦值的資訊,比如KEY=val cmd它們可能會或可能不會“影響目前的執行環境”,但沒有關於重定向、$擴展和外部命令之間的互動。

並且它不可能也適用於作為$-expansions 的一部分完成的變數分配,因為無論是外部命令還是內置命令,都會ls $((i=2+3))導致在所有 shelli中設置為。5``ls

這是未指定的,因此每個外殼都可以做它想做的事情,並且不必記錄細節。(從歷史的角度來看,這是未指定的,因為不同的貝殼做不同的事情。)從技術上講,貝殼可以翻轉硬幣。在實踐中,在單獨的環境中執行和不執行的細節可能取決於在某些情況下所做的優化,例如取決於命令是否是內置的,取決於重定向是否為 to/from /dev/null,取決於陷阱是否處於活動狀態,取決於是否set -e生效,取決於命令是否是列表中的最後一個,等等。

來自SUSv4 (POSIX.1-2008) “Shell and Utilities” — §2.9.1 “簡單命令”

如果命令名稱不是特殊的內置實用程序或函式,則應為命令的執行環境導出變數分配,並且不應影響目前執行環境,除非是在步驟 4 中執行的擴展的副作用。在這種情況下,它是未指定的:

  • 分配對於步驟 4 中的後續擴展是否可見
  • 作為這些擴展的副作用進行的變數分配對於步驟 4 中的後續擴展或目前 shell 執行環境中的後續擴展是否可見,或兩者兼而有之

澄清一下:“第 4 步”包括參數擴展(例如${i:=7})和算術擴展(例如$((i=7)))。“不應影響目前執行環境”的變數擴展是命令前面的變數擴展,例如i=7 ls. 所以這一段說,除其他外,如果參數擴展或變數擴展修改了變數的值,在命令返回後是否有效果是未指定的。

在實踐中,shell 通常通過先分叉並在子 shell 中執行重定向來將重定向應用到外部命令。但是它們的不同之處在於它們是在創建子shell之前還是之後確定重定向的目標。

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