シェルの深さはいくつですか?


73

問題:シェルの深さを確認してください。

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

シェルの深さがいくつあるかを知りたいのですが、シェル画面に常に表示されているかもしれません。(その部分を管理できます)。

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

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


27
された$SHLVL変数は、あなたが探しているもの(いくつかのシェルによって維持されていますか)?
ステファンシャゼラス

1
明確にするために、SHLVLで示される(直接ネストされた)シェルの数にはあまり興味がありませんが、現在のシェルがvimの子孫であるかどうかはわかりませんか?
ジェフシャラー

14
これは少しXYの問題のようです-私のワークフローは^ Zであり、vimインスタンスから親シェルにエスケープしてfg戻りますが、この問題はありません。
ドアノブ

2
@Doorknob、私もそうします。しかし、「ジョブ」をチェックし続けなければならないので、私はこれを好む。そして、私のマシン上で時々実行することができます。次に、式を使用してTMUXを追加します。それは複雑で溢れ出します。vim内でシェルを生成すると、分散は少なくなります。(しかし、私は混乱し、それゆえ質問をすることになります)。
プラナイ

3
@Doorknob:敬意を払って、「A地点からB地点までどのように運転しますか?」という質問に答えているように思えます。「運転しないでください。ユーザーが複数のファイルを同時に編集するワークフローを持っている場合、複数の並列停止vimジョブを持つことは、ネストされたプロセスのスタックを持つよりも混乱する可能性があります。ちなみに、複数のウィンドウを使用することを好み、簡単にすばやく前後にジャンプできますが、別のワークフローを好むという理由だけでこれをXYの問題とは呼びません。
スコット

回答:


45

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

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

これはSHLVLvimコマンドを入力するたびに自動的かつサイレントにインクリメントします。これは、使用するvi/の各バリアントに対して行う必要がありvimます。例えば、

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

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


上記にはわずかな非効率性があります。いくつかのシェル(bashは1つ)で、あなたが言うなら

cmd 1 ;  cmd 2 ;;  cmd n

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

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

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

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

しかし、この論理により、あなたが言うならexec sh、あなたSHLVLは上がるべきです。しかし、実際のシェルレベルは増加していないため、これは望ましくありません。シェル は、以下を実行するときに1 引くことでこれを処理SHLVLします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(彼はすべてを知っている)よると、一部のシェルは、サブシェル内にある場合、これを行わないほどスマートexecです。

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

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 "$@");}

個人設定:のような
名前に変更VILVLすることができVIM_LEVELます。「VILVL」を見ると、目が痛い。彼らは、それが「ビニル」のスペルミスか、不正なローマ数字かどうかを見分けることができません。


サポートしていないシェルSHLVL(ダッシュなど)を使用している場合、シェルがスタートアップファイルを実装している限り、自分でシェルを実装できます。次のようなことをしてください

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

あなたの.profileファイルまたは該当するファイル。(おそらく、名前を使用しないでSHLVLください。これをサポートするシェルを使用し始めると混乱が発生しますSHLVL。)


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


1
シェルビルトインでこれを行うことができる場合、psまたはのような外部実行可能プログラムの実行が非常に多くの答えから示唆されていることに、少し戸惑いpstreeます。
スコット

この答えは完璧です。私はこれを解決策としてマークしました(残念ながら、まだ多くの票がありません)。
プラナイ

あなたのアプローチは素晴らしく、プリミティブのみを使用しているため、これを.profile / .shellrcに含めても何も壊れません。作業中のマシンでそれらをプルします。
プラナイ

1
dash算術展開があることに注意してください。SHELL_LEVEL=$((SHELL_LEVEL + 1))$ SHELL_LEVELが以前に設定されていないか空であったとしても、十分なはずです。Bourneシェルへの移植expr性が必要な場合にのみ使用$(...)する必要がありますが、次にに置き換える必要もあります`..`SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
ステファンシャゼラス

2
@Pranay、それは問題になることはほとんどありません。攻撃者が注入できる場合は任意の任意のenv VARを、その後、PATH / LD_PRELOADのようなものは、より多くの明白な選択肢ですが、sudoをしてreset_envなしで設定のように問題のない変数は、を介して取得(と1が強制することができた場合bashで〜/ .bashrcのを読み取るために、スクリプトをたとえば、stdinをソケットにする)、それが問題になる可能性があります。それは多くの「if」ですが、心の奥に置いておくべきものです(算術コンテキストでの非サニタイズデータは危険です)
ステファンシャゼル

37

セッションリーダーを見つけるまで、プロセスツリーを上るのに必要な時間をカウントできます。同様にzshLinux上で:

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、彼らはバグだらけじゃない(が、多くがある場合、サブシェルです)))。ただし、プロセスのネストではなく、シェルのネストの量を追跡するだけです。また、レベル0がセッションリーダーであるとは限りません。


うん..これまたは類似した。私は自分でこれを書きたくありませんでしたし、誰かにこれを書いてもらうつもりはありませんでした。:(。vimやシェルの機能、または定期的にメンテナンスされるプラグインを望んでいました。検索しましたが、何も見つかりませんでした
。– Pranay

31

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


2
以下のための作品bashではなく、ためdash
agc

SHLVLは私を助けません。私はそれについて知っていました、そして、私が検索したとき、それは検索でも出てきました。:)質問には詳細があります。
プラナイ

@Pranay vim自体はこの情報を提供しませんか?
user2497

@ user2497、私はやや。これが問題の前提です。私はどこでも検索しましたが、私もSHLVLを知っていました。私が欲しかった-> a)そのようなことはないことを確認してください。b)最小限の依存関係/メンテナンスでそれを行う。
プラナイ

16

1つの潜在的な解決策は、の出力を調べることですpstree。内から生成されたシェル内で実行するとvi、リストされているツリーツリーの部分に、pstree自分の深さが表示されます。例えば:

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

ええ、それは私が解決策として提案したものです(質問で)私はpstreeを解析したくありません:(。これは手動で読むのに適しています、私のためにそれを行うプログラムを書くことを考えていました。プラグイン/ツールはすでにそれを行います:)。
プラナイ

11

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

シンプルなソリューションbash.bashrc次の2行に追加(または現在のPS1値を変更):

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

結果:

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

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

ネストされたvimレベルとシェルレベルの両方を持つ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-シェルの深さレベル


これにより、bashの入れ子になります。それは私にvimの巣を与えません。:)
プラナイ

@Pranay新しいソリューションを確認してください。あなたがやりたいことをやっています。
MiniMax

ええ、これは良い解決策です。私はさらにシェルを追加することができ、それはうまくいくでしょう:)。
Pranay

8

質問であなたは構文解析に言及しました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

ええ、私はこれをかなりやっていました。また、「bash」や「vim」などを数えるものもありました。それを維持したくないだけです。また、多くのVMを切り替えて時々開発する必要がある場合、多くのカスタム機能を使用することはできません。
プラナイ

3

これは厳密に質問答えるものではありませんが、多くの場合、そうする必要はありません。

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

あなたがトップレベルのシェルにいると思い、確実にしたいときは、Ctrl-Dを入力することを習慣にしてください。

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

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

Use "logout" to leave the shell.

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


1
これは間違いなく助けになり、はい、それは多くの合併症を取り除きます:)。私はこれを受け入れられた答えと組み合わせるかもしれません:))。条件付きで設定されているため、常にプロンプ​​トを確認する必要はありません。
プラナイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.