Bash

$PROMPT_COMMAND 是冒號分隔的列表嗎?

  • October 12, 2021

我想.bash_history通過設置PROMPT_COMMAND在所有終端選項卡和視窗中啟用我的命令歷史記錄.profile

export PROMPT_COMMAND="history -a; history -c; history -r;$PROMPT_COMMAND"

但是,當我檢查此環境變數是否已設置時,我得到:

echo $PROMPT_COMMAND
printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"

像這樣導出會PROMPT_COMMAND覆蓋我現有的$PROMPT_COMMAND列表,還是有必要在導出之前PROMPT_COMMAND用 a作為前綴?:

作為 google 搜尋時的最佳結果之一"PROMPT_COMMAND seperator",我覺得答案值得比迄今為止給出的更多努力。

**注意:**在準備這篇文章時,我測試了 Bash 版本 3.2、4.4、5.0 和 5.1(通過docker)並查看了相同版本的原始碼(通過gnu.org

讓我們開始 …


Bash 手冊

重擊 <= v5.0

變數 PROMPT_COMMAND 的值在之前檢查
Bash 列印每個主要提示。如果設置了 PROMPT_COMMAND 並且
有一個非空值,那麼這個值就像它一樣被執行
已在命令行中鍵入。

這是什麼意思?

這意味著 bash 一般相當於:

eval "${PROMPT_COMMAND:-}"

因此,任何適用於此的命令連結/排序eval都是允許的。

重擊 >= v5.1

Bash 檢查數組變數 PROMPT_COMMAND 的值
就在列印每個主要提示之前。

如果 PROMPT_COMMAND 中的任何元素已設置且非空,則 Bash
按數字順序執行每個值,就好像它已經
在命令行中鍵入。
...
如果設置了這個變數,並且是一個數組,那麼每個變數的值
set 元素被解釋為要執行的命令
在列印主要提示 ($PS1) 之前。

如果設置了但不是數組變數,則使用其值
作為要執行的命令。

這是什麼意思?

這意味著 bash 一般相當於:

if [[ ${#PROMPT_COMMAND[@]} -gt 1 ]]; then
   for cmd in "${PROMPT_COMMAND[@]}"; do eval "${cmd:-}"; done
else
   eval "${PROMPT_COMMAND:-}"
fi

即數組的每個元素都是eval獨立的

社區在做什麼

在github上搜尋"PROMPT_COMMAND"顯示:

  • 總的來說,社區;用作分隔符
  • 某些事件$'\n'用作分隔符

我具體看到的

作為軼事說明:

  • Mac OSX 使用;(參見/etc/bashrc_Apple_Terminal
  • Bash-PreExec 似乎使用$'\n'
  • Bash-It使用;

讓我們看看他們的行動

首先,讓我們獲得一個全新的 Bash v3.2 環境:

docker run -it --rm bash:3.2
bash-3.2$ 

分號分隔的字元串

使用;-delimited 字元串進行測試會產生:

bash-3.2$ PROMPT_COMMAND='printf 1;printf 2;printf 3;printf ":\n"'
123:
bash-3.2$ 

行分隔的字元串

\n使用-delimited 字元串對此進行測試會產生:

bash-3.2$ PROMPT_COMMAND=$'printf 1\nprintf 2\nprintf 3\nprintf ":\n"'
123:
bash-3.2$ 

混合兩者

我們可以混合使用兩種分隔符嗎?呸!:

bash-3.2$ PROMPT_COMMAND=$'printf 1;printf 2\nprintf 3;printf ":\n"'
123:
bash-3.2$ 

陣列怎麼樣?

我們要去哪裡,我們需要 Bash v5.1

docker run -it --rm bash:5.1
bash-5.1$ 

文字數組

使用文字數組對此進行測試會產生:

bash-5.1$ PROMPT_COMMAND=( "printf 1" "printf 2" "printf 3" "printf ':\n'" )
123:
bash-5.1$ 
使用所有三個!

如果你把它們都混合起來怎麼辦:

bash-5.1$ PROMPT_COMMAND=( "printf 1" $'printf 2\nprintf 3' 'printf 4;printf ":\n"' )
1234:
bash-5.1$ 

使用哪一個?

如果${PROMP_COMMAND}是一個數組,您應該將您的命令附加/前置到數組中。

注意,如果您願意,您可以將命令組(即,單行;$'\n'分隔的命令字元串)作為單個條目附加到數組中。

如果${PROMP_COMMAND}不是數組,那麼您可能應該在添加命令時使用;,因為它感覺像是一個更正式的分隔符,但知道您添加的命令可以是更複雜的腳本,這些腳本本身可以包含分隔的步驟由$'\n’。

我會嘗試使用 munging 功能嗎?

當然 !

##
# pc_munge munges PROMPT_COMMAND.
# Tries to accommodate when PROMPT_COMMAND is an array (supported in Bash v5.1+).
# If ${#PROMPT_COMMAND[@]} has 2+ elements, then we treat as an array, otherwise
# we defensively treat it as a string.
# By default, uses ';' as separator.
#
# NOTE: Does NOT check if command is already present
#
# Parms:
#  $1 command to add
#  $2 before | after (default: after)
#  $3 separator (default: ';')
#
pc_munge() {
   [[ -n "$1" ]] || return
   local fs="${3:-;}" # null | '' =&gt; default
   case "${2:-after}" in
       before)
           [[ ${#PROMPT_COMMAND[@]} -gt 1 ]] && PROMPT_COMMAND=( "$1" "${PROMPT_COMMAND[@]}" ) || PROMPT_COMMAND="${1}${PROMPT_COMMAND:+${fs}$PROMPT_COMMAND}"
           ;;
       *) # after
           [[ ${#PROMPT_COMMAND[@]} -gt 1 ]] && PROMPT_COMMAND+=( "$1" ) || PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND${fs}}${1}"
           ;;
   esac
}

bash $PROMPT_COMMAND(或者每個元素,如果它是一個帶有 bash 5.1+ 的數組,正如@DavidFarrell 已經指出的那樣)被解釋為 shell 程式碼,所以你可以像編寫 shell 腳本一樣編寫它的內容。在 shell 腳本中,命令可以用;, newline, |, &, &&,||等分隔,每個命令都有自己的含義。

所以你可以有例如:

PROMPT_COMMAND='
 cmd1 && cmd2 &
 cmd3; cmd4
 for cmd in cmd5 cmd6; do
   "$cmd"
 done
 cmd7 &lt;&lt; "EOF"
foo bar
EOF'

等等

要將命令附加到已設置的命令$PROMPT_COMMAND並確保它獨立於之前的命令執行,您可以選擇;newline,但如果最初為空或在此處以文件分隔符結尾的特殊情況下;將不起作用$PROMPT_COMMAND(就像我們EOF上面的),這樣做:

PROMPT_COMMAND+='
 your extra command here'

(或舊版本:

PROMPT_COMMAND=$PROMPT_COMMAND'
 your extra command here'

)

更可取。或者:

PROMPT_COMMAND='your extra command here
'$PROMPT_COMMAND

預先添加它,以便它首先執行。

使用 bash 5.1+,您還可以附加一個數組元素:

PROMPT_COMMAND+=(
 'your extra command here'
)

或前置:

PROMPT_COMMAND=(
 'your extra command here'
 "${PROMPT_COMMAND[@]}"
)

這留下了一個潛在的問題:如果啟用了errexit( -e) 選項,並且這些腳本中的任何命令都失敗了,則處理會在那裡停止¹,因此您的額外命令可能最終不會執行。不過,這可能應該被視為一個病態的案例。errexit在互動式 shell 中使用可能是個壞主意(在腳本中使用時已經頗有爭議)。

為了比較,在 中zsh,而不是在 中$PROMPT_COMMAND,您有在不同點呼叫的鉤子函式,其靈感來自(並改進了)中類似命名的特殊別名。是您要定義的函式,以便在每個提示之前呼叫它。在中,您也可以改為使用要呼叫的額外函式來設置特殊數組(也會影響那裡的處理¹)。tcsh``precmd()``zsh``precmd_functions``errexit


¹ 選項也可能發生同樣的情況,nounset或者在處理過程中遇到語法錯誤或任何類型的致命錯誤。

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