Bash

有狀態的 bash 函式

  • July 16, 2014

我想在 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最可靠的方法是使用$IFSsplit 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 \backspaces 然後試圖平衡他們的字元數……呃。所以我這樣做:

> > PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}' > > > 再次,${parameter##expansion}節省了一天。不過這裡有點奇怪——我們實際上是在剝離變數的同時設置變數。我們使用它的新值 - set mid-strip - 作為我們從中剝離的 glob。你看?我們##*從增量變數的頭部到最後一個字元剝離所有內容,該字元可以是[$((PS2c=0))-9]. 我們以這種方式保證不輸出值,但我們仍然分配它。這很酷 - 我以前從未這樣做過。但是 POSIX 也向我們保證,這是可以做到的最便攜的方式。 > > >

這要感謝 POSIX 指定${parameter} $((expansion))的將這些定義保留在目前 shell 中,而無需我們將它們設置在單獨的子 shell 中,無論我們在哪裡評估它們。這就是為什麼它在dashand中和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 >

上海展示

它在bashor中的工作方式相同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 >

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