web-dev-qa-db-ja.com

私はいくつの殻の深さですか?

問題:私の深さを数えます。

詳細:vimからシェルをたくさん開きます。ビルドして実行し、終了します。時々私は忘れて、別のvimを開いてから、さらに別のシェルを開きます。 :(

私はいくつのシェルの深さを知りたいと思っています。おそらく、シェルの画面に常に表示されていることでしょう。 (私はその部分を管理できます)。

私の解決策:プロセスツリーを解析してvimとbash/zshを探し、現在のプロセスの深さを把握します。

そのようなものはすでに存在していますか?何も見つかりませんでした。

74
Pranay

あなたの質問を読んだとき、私の最初の考えは$SHLVLでした。次に、vimレベルに加えてシェルレベルをカウントする必要があることを確認しました。これを行う簡単な方法は、シェル関数を定義することです。

vim()  { ( ((SHLVL++)); command vim  "$@");}

これにより、SHLVLコマンドを入力するたびに、自動的にvimが自動的にインクリメントされます。これは、これまでに使用したvi/vimのバリアントごとに行う必要があります。例えば。、

vi()   { ( ((SHLVL++)); command vi   "$@");}
view() { ( ((SHLVL++)); command view "$@");}

括弧の外側のセットはサブシェルを作成するため、SHLVLの値を手動で変更しても、現在の(親)シェル環境が汚染されることはありません。もちろん、commandキーワードは、関数が自分自身を呼び出すのを防ぐためにあります(無限再帰ループになります)。そしてもちろん、これらの定義を.bashrcまたはその他のシェル初期化ファイルに挿入する必要があります。


上記にはわずかな非効率性があります。一部のシェル(bashは1つ)では、

cmd1;cmd2; …;cmd

ここで、cmdnは外部の実行可能プログラム(つまり、組み込みコマンドではありません)であり、cmdnが終了するのを待つだけのために、シェルは余分なプロセスを保持します。これは(おそらく)不要です。長所と短所は議論の余地があります。少しのメモリとプロセススロットを占有することを気にしない場合(およびpsを実行するときに必要以上のシェルプロセスを表示する場合)、上記を実行して、次のセクションにスキップしてください。追加のプロセスを維持しないシェルを使用している場合は、同上。ただし、余分なプロセスを回避したい場合は、最初に試すことです

vim()  { ( ((SHLVL++)); exec vim  "$@");}

execコマンドは、余分なシェルプロセスが残るのを防ぐためにあります。

しかし、落とし穴があります。シェルのSHLVLの処理は少し直感的です。シェルが起動すると、SHLVLが設定されているかどうかを確認します。設定されていない場合(または数値以外に設定されている場合)、シェルはそれを1に設定します。設定されている場合(数値に設定されている場合)、シェルは1を追加します。

しかし、このロジックにより、exec shと言うと、SHLVLが上がるはずです。しかし、それは望ましくありません。実際のシェルレベルが増加していないからです。シェルはこれを1を引くfromSHLVLで処理します。 exec

$ echo "$SHLVL"
1

$ set | grep SHLVL
SHLVL=1

$ env | grep SHLVL
SHLVL=1

$ (env | grep SHLVL)
SHLVL=1

$ (env) | grep SHLVL
SHLVL=1

$ (exec env) | grep SHLVL
SHLVL=0

そう

vim()  { ( ((SHLVL++)); exec vim  "$@");}

ウォッシュです。 SHLVLをインクリメントするのは、再度デクリメントする場合のみです。関数の恩恵を受けずに、vimとだけ言ってもよいでしょう。

注:
すべてを知っているStéphaneChazelasによれば 、いくつかのシェルは十分賢いnotexecがサブシェルにある場合にこれを行うには。

これを修正するには、次のようにします

vim()  { ( ((SHLVL+=2)); exec vim  "$@");}

次に、vimレベル独立してシェルレベルをカウントする必要があることを確認しました。まあ、まったく同じトリックが機能します(まあ、マイナーな変更を加えます):

vim() { ( ((SHLVL++, VILVL++)); export VILVL; exec vim "$@");}

viviewなどについても同様です)。exportはデフォルトでは環境変数として定義されていないため、VILVLが必要です。ただし、関数の一部である必要はありません。 export VILVLを別のコマンドとして(.bashrcで)言うことができます。また、上記のように、追加のシェルプロセスが問題でない場合は、command vimの代わりにexec vimを実行し、SHLVLをそのままにすることができます。

vim() { ( ((VILVL++)); command vim "$@");}

個人の好み:
VILVLVIM_LEVELのような名前に変更したい場合があります。 「VILVL」を見ると目が痛い。 「ビニール」のつづりが間違っているのか、ローマ数字の形式に誤りがあるのか​​はわかりません。


SHLVL(ダッシュなど)をサポートしないシェルを使用している場合は、シェルがスタートアップファイルを実装している限り、自分で実装できます。ちょうど何かをする

if [ "$Shell_LEVEL" = "" ]
then
    Shell_LEVEL=1
else
    Shell_LEVEL=$(expr "$Shell_LEVEL" + 1)
fi
export Shell_LEVEL

.profileまたは該当するファイル内。 (SHLVLをサポートするシェルの使用を開始すると混乱を招くため、SHLVLという名前は使用しないでください。)


他の回答では、環境変数値をシェルプロンプトに埋め込む問題に対処しているので、繰り返しはしません。特に、方法をすでに知っているとおっしゃっています。

46
Scott

セッションリーダーが見つかるまで、プロセスツリーを上るのに必要な時間を数えることができます。 Linuxのzshと同様:

lvl() {
  local n=0 pid=$$ buf
  until
    IFS= read -rd '' buf < /proc/$pid/stat
    set -- ${(s: :)buf##*\)}
    ((pid == $4))
  do
    ((n++))
    pid=$2
  done
  echo $n
}

またはPOSIXly(ただし、効率が悪い):

lvl() (
  unset IFS
  pid=$$ n=0
  until
    set -- $(ps -o ppid= -o sid= -p "$pid")
    [ "$pid" -eq "$2" ]
  do
    n=$((n + 1)) pid=$1
  done
  echo "$n"
)

これにより、ターミナルエミュレータまたはgettyによって起動されたシェルに0が、子孫にさらに1つ追加されます。

起動時に一度だけ実行する必要があります。たとえば:

PS1="[$(lvl)]$PS1"

あなたの~/.zshrcまたは同等のものをプロンプトに表示します。

tcshと他のいくつかのシェル(zshksh93fishおよびbash少なくとも)$SHLVL変数は、起動時にインクリメントします(そして、execで別のコマンドを実行する前にデクリメントします(バグがなければ(ただし、多くの場合)execがサブシェルにある場合を除きます)))。ただし、Shellネストの量のみを追跡し、プロセスネストは追跡しません。また、レベル0がセッションリーダーであるとは限りません。

37

使用する echo $SHLVLKISS原則 を使用します。プログラムの複雑さによっては、これで十分な場合があります。

31
user2497

解決策の1つは、pstreeの出力を確認することです。 vi内から生成されたシェル内で実行すると、ツリーツリーのpstreeをリストする部分が、自分の深さを示しているはずです。例えば:

$ pstree <my-user-ID>
...
       ├─gnome-terminal-─┬─bash───vi───sh───vi───sh───pstree
...
16
John

最初のバリアント-シェルの深さのみ。

bashの簡単な解決策:.bashrcの次の2行に追加(または現在のPS1の値を変更):

PS1="${SHLVL} \w\$ "
export PS1

結果:

1 ~$ bash
2 ~$ bash
3 ~$ exit
exit
2 ~$ exit
exit
1 ~$

プロンプト文字列の先頭の数字はシェルレベルを示します。

ネストされたvimレベルとShellレベルの両方を持つ2番目のバリアント。

この行を.bashrcに追加します

branch=$(pstree -ls $$)
vim_lvl=$(grep -o vim <<< "$branch" | wc -l)
sh_lvl=$(grep -o bash <<< "$branch" | wc -l)
PS1="v:${vim_lvl};s:$((sh_lvl - 1)):\w\$ "
export PS1

結果:

v:0;s:1:/etc$ bash
v:0;s:2:/etc$ bash
v:0;s:3:/etc$ vim
##### do ':sh' command in the vim, Shell level is increasing by 1
v:1;s:4:/etc$ vim
##### do ':sh' command in the vim, Shell level is increasing by 1
v:2;s:5:/etc$ bash
v:2;s:6:/etc$

v:1-vim深度レベル
s:3-シェルの深さレベル

11
MiniMax

質問では、pstreeの解析について述べました。これは比較的簡単な方法です:

bash-4.3$ pstree -Aals $$ | grep -E '^ *`-((|ba|da|k|c|tc|z)sh|vim?)( |$)'
                  `-bash
                      `-bash --posix
                          `-vi -y
                              `-dash
                                  `-vim testfile.txt
                                      `-tcsh
                                          `-csh
                                              `-sh -
                                                  `-zsh
                                                      `-bash --norc --verbose

pstreeオプション:

  • -A-ASCIIフィルタリングを容易にするための出力(この場合、すべてのコマンドの前に`-
  • -a-コマンド引数も表示します。副作用として、すべてのコマンドが別々の行に表示され、grepを使用して出力を簡単にフィルタリングできます。
  • -l-長い行を切り捨てません
  • -s-選択したプロセスの親を表示します
    (残念ながら古いバージョンのpstreeではサポートされていません)
  • $$-選択されたプロセス-現在のシェルのPID
8
pabouk

これは厳密には質問に答えませんが、多くの場合、そうする必要はないかもしれません:

シェルを初めて起動するときに、set -o ignoreeofを実行します。 しないでください~/.bashrcに入れてください。

最上位のシェルを使用していることを確認し、確認したい場合は、Ctrl-Dを入力することを習慣にしてください。

最上位のシェルでではない場合、Ctrl-Dは現在のシェルに「入力の終了」を通知し、1レベル下に戻ります。

トップレベルのシェルでareの場合、次のメッセージが表示されます。

Use "logout" to leave the Shell.

私はこれを常にチェーンSSHセッションに使用して、SSHチェーンの特定のレベルに簡単に戻すことができるようにしています。ネストされたシェルでも同様に機能します。

3
Wildcard