有狀態的 bash 函式
我想在 Bash 中實現一個函式,每次呼叫都會增加(並返回)一個計數。不幸的是,這似乎並不簡單,因為我在子 shell 中呼叫函式,因此它無法修改其父 shell 的變數。
這是我的嘗試:
PS_COUNT=0 ps_count_inc() { let PS_COUNT=PS_COUNT+1 echo $PS_COUNT } ps_count_reset() { let PS_COUNT=0 }
這將按如下方式使用(因此我需要從子shell呼叫函式):
PS1='$(ps_count_reset)> ' PS2='$(ps_count_inc) '
這樣,我會有一個編號的多行提示:
> echo 'this 1 is 2 a 3 test'
可愛的。但由於上述限制不起作用。
一個無效的解決方案是將計數寫入文件而不是變數。但是,這會在多個同時執行的會話之間產生衝突。當然,我可以將 shell 的程序 ID 附加到文件名中。但我希望有一個更好的解決方案,它不會讓我的系統因大量文件而混亂。
要獲得您在問題中記錄的相同輸出,所需要的只是:
PS1='${PS2c##*[$((PS2c=0))-9]}- > ' PS2='$((PS2c=PS2c+1)) > '
你不需要扭曲。這兩行將在任何偽裝成任何接近 POSIX 兼容性的 shell 中完成。
- > cat <<HD 1 > line 1 2 > line $((PS2c-1)) 3 > HD line 1 line 2 - > echo $PS2c 0
但我喜歡這個。我想展示使這項工作變得更好的基本原理。所以我稍微編輯了一下。我現在把它卡住了,
/tmp
但我想我也會為自己保留它。它在這裡:cat /tmp/prompt
提示腳本:
ps1() { IFS=/ set -- ${PWD%"${last=${PWD##/*/}}"} printf "${1+%c/}" "$@" printf "$last > " } PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}' PS2='$((PS2c=PS2c+1)) > '
注意:最近了解了yash,我昨天建構了它。無論出於何種原因,它都不會使用
%c
字元串列印每個參數的第一個字節——儘管文件是特定於該格式的寬字元擴展的,因此它可能是相關的——但它與%.1s
這就是全部。上面有兩件主要的事情。這就是它的樣子:
/u/s/m/man3 > cat <<HERE 1 > line 1 2 > line 2 3 > line $((PS2c-1)) 4 > HERE line 1 line 2 line 3 /u/s/m/man3 >
解析
$PWD
每次
$PS1
評估它都會解析並列印$PWD
以添加到提示中。但我不喜歡整個$PWD
螢幕擠滿我的螢幕,所以我只想要目前路徑中每個麵包屑的第一個字母到目前目錄,我想完整地看到它。像這樣:/h/mikeserv > cd /etc /etc > cd /usr/share/man/man3 /u/s/m/man3 > cd / / > cd ~ /h/mikeserv >
這裡有幾個步驟:
> >
IFS=/
> > > 我們將不得不拆分目前$PWD
最可靠的方法是使用$IFS
split on/
。之後就不需要再費心了——從這裡開始的所有拆分都將由$@
下一個命令中的 shell 位置參數數組定義,例如: > > >set -- ${PWD%"${last=${PWD##/*/}}"}
> > > 所以這個有點棘手,但主要是我們$PWD
在/
符號上分裂。$last
在最左邊和最右邊/
斜線之間出現任何值之後,我還使用參數擴展來分配所有內容。通過這種方式,我知道如果我只是在/
並且只有一個/
,那麼$last
仍然會等於整體$PWD
並且$1
會是空的。這很重要。在將$last
其$PWD
分配給$@
. > > >printf "${1+%c/}" "$@"
> > > 所以在這裡 - 只要${1+is set}
我們每個 shell 參數printf
的第一個%c
字元 - 我們剛剛設置到目前目錄中的每個目錄$PWD
- 減去頂層目錄 - 拆分為/
. 所以我們基本上只是列印$PWD
除頂部之外的每個目錄的第一個字元。重要的是要意識到只有$1
在完全設置時才會發生這種情況,這不會發生在 root/
或從/
諸如 in 中刪除的某個位置/etc
。 > > >printf "$last > "
> > >$last
是我剛剛分配給頂層目錄的變數。所以現在這是我們的頂級目錄。它列印最後一條語句是否執行。它需要一點點>
才能很好地衡量。 > > >但是增量呢?
然後是
$PS2
有條件的問題。我之前展示瞭如何做到這一點,您仍然可以在下面找到 - 這基本上是一個範圍問題。但是還有更多的東西,除非你想開始做一堆printf \b
ackspaces 然後試圖平衡他們的字元數……呃。所以我這樣做:> >
PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
> > > 再次,${parameter##expansion}
節省了一天。不過這裡有點奇怪——我們實際上是在剝離變數的同時設置變數。我們使用它的新值 - set mid-strip - 作為我們從中剝離的 glob。你看?我們##*
從增量變數的頭部到最後一個字元剝離所有內容,該字元可以是[$((PS2c=0))-9]
. 我們以這種方式保證不輸出值,但我們仍然分配它。這很酷 - 我以前從未這樣做過。但是 POSIX 也向我們保證,這是可以做到的最便攜的方式。 > > >這要感謝 POSIX 指定
${parameter} $((expansion))
的將這些定義保留在目前 shell 中,而無需我們將它們設置在單獨的子 shell 中,無論我們在哪裡評估它們。這就是為什麼它在dash
and中和sh
在 中一樣有效的原因。我們不使用依賴於 shell/終端的轉義,我們讓變數自己測試。這就是使可移植程式碼快速的原因。bash``zsh
其餘的相當簡單 - 每次
$PS2
評估時增加我們的計數器,直到$PS1
再次重置它。像這樣:> >
PS2='$((PS2c=PS2c+1)) > '
> > >所以現在我可以:
短跑展示
ENV=/tmp/prompt dash -i /h/mikeserv > cd /etc /etc > cd /usr/share/man/man3 /u/s/m/man3 > cat <<HERE 1 > line 1 2 > line 2 3 > line $((PS2c-1)) 4 > HERE line 1 line 2 line 3 /u/s/m/man3 > printf '\t%s\n' "$PS1" "$PS2" "$PS2c" $(ps1)${PS2c##*[$((PS2c=0))-9]} $((PS2c=PS2c+1)) > 0 /u/s/m/man3 > cd ~ /h/mikeserv >
上海展示
它在
bash
or中的工作方式相同sh
:ENV=/tmp/prompt sh -i /h/mikeserv > cat <<HEREDOC 1 > $( echo $PS2c ) 2 > $( echo $PS1 ) 3 > $( echo $PS2 ) 4 > HEREDOC 4 $(ps1)${PS2c##*[$((PS2c=0))-9]} $((PS2c=PS2c+1)) > /h/mikeserv > echo $PS2c ; cd / 0 / > cd /usr/share /u/share > cd ~ /h/mikeserv > exit
正如我上面所說,主要問題是您需要考慮在哪裡進行計算。您沒有在父 shell 中獲得狀態 - 所以您不會在那裡計算。您在子外殼中獲得狀態 - 這就是您計算的地方。但是您在父 shell 中進行定義。
ENV=/dev/fd/3 sh -i 3<<\PROMPT ps1() { printf '$((PS2c=0)) > ' ; } ps2() { printf '$((PS2c=PS2c+1)) > ' ; } PS1=$(ps1) PS2=$(ps2) PROMPT 0 > cat <<MULTI_LINE 1 > $(echo this will be line 1) 2 > $(echo and this line 2) 3 > $(echo here is line 3) 4 > MULTI_LINE this will be line 1 and this line 2 here is line 3 0 >