ฉันมีกระสุนจำนวนเท่าไร


73

ปัญหา : ค้นหาว่าฉันมีกระสุนจำนวนเท่าไร

รายละเอียด : ฉันเปิดเปลือกจากกลุ่มมาก สร้างและเรียกใช้และออกจาก บางครั้งฉันลืมและเปิดเสียงเรียกเข้าภายในจากนั้นก็อีกเปลือก :(

ฉันอยากรู้ว่าฉันอยู่ลึกเท่าไรในเปลือกหอยฉันอาจจะมีมันบนหน้าจอเชลล์ของฉันตลอดเวลา (ฉันสามารถจัดการส่วนนั้น)

โซลูชันของฉัน : แยกแผนผังกระบวนการและมองหา vim และ bash / zsh และค้นหาความลึกของกระบวนการปัจจุบันที่อยู่ภายใน

มีบางอย่างเช่นนั้นที่มีอยู่แล้วหรือไม่? ฉันหาอะไรไม่เจอ


27
เป็น$SHLVLตัวแปร (ดูแลโดยหลายเปลือกหอย) สิ่งที่คุณกำลังมองหา?
Stéphane Chazelas

1
ในการชี้แจงคุณไม่สนใจจริงๆว่ามีเชลล์จำนวนเท่าใด (ซ้อนกันโดยตรง) ที่ระบุโดย SHLVL แต่เชลล์ปัจจุบันของคุณเป็นลูกหลานของกลุ่มหรือไม่
Jeff Schaller

14
นี่น่าจะเป็นปัญหาเล็กน้อยของ XY - เวิร์กโฟลว์ของฉันคือ ^ Z ที่จะหลบหนีจากอินสแตนซ์ที่เป็นกลุ่มลงในเชลล์พาเรนต์และfgเพื่อกลับไปที่ไม่มีปัญหานี้
Doorknob

2
@ Doorknob ฉันก็ทำเช่นนั้น แต่ฉันชอบอันนี้เพราะฉันจะต้องตรวจสอบ "งาน" ต่อไป และในเครื่องของฉันสามารถทำงานได้หลายครั้ง ตอนนี้เพิ่ม TMUX ด้วยสมการ มันซับซ้อนและล้น ถ้าฉันวางไข่เปลือกในกลุ่มมันจะน้อยกว่ากระจาย (อย่างไรก็ตามฉันท้ายทำเลอะและด้วยเหตุนี้คำถาม)
Pranay

3
@ Doorknob: ด้วยความเคารพอย่างสูงดูเหมือนจะตอบคำถาม“ ฉันจะขับรถจากจุด A จากจุด B ได้อย่างไร” พร้อมคำแนะนำว่า“ อย่าขับรถ; เพียงแค่ใช้ Uber” หากผู้ใช้มีเวิร์กโฟลว์ที่เกี่ยวข้องกับการแก้ไขไฟล์หลายไฟล์พร้อมกันการมีvimงานที่หยุดแบบขนานหลายครั้งอาจทำให้เกิดความสับสนมากกว่าการมีกระบวนการซ้อนกันหลายชั้น โดยที่ฉันชอบมีหลายwindowsดังนั้นฉันสามารถข้ามไปมาได้อย่างรวดเร็ว แต่ฉันจะไม่เรียกสิ่งนี้ว่าปัญหา XY เพียงเพราะฉันชอบเวิร์กโฟลว์ที่แตกต่างกัน
สกอตต์

คำตอบ:


45

$SHLVLเมื่อฉันอ่านคำถามของคุณความคิดแรกของฉันคือ จากนั้นฉันเห็นว่าคุณต้องการนับvimระดับ นอกเหนือจากระดับเชลล์ วิธีง่ายๆในการทำเช่นนี้คือการกำหนดฟังก์ชั่นเชลล์:

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

การดำเนินการนี้จะเพิ่มขึ้นโดยอัตโนมัติSHLVL ในแต่ละครั้งที่คุณพิมพ์vimคำสั่ง คุณจะต้องทำสิ่งนี้กับตัวแปรแต่ละตัวvi/ vimที่คุณเคยใช้ เช่น,

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

วงเล็บด้านนอกสร้างSHLVL เชลล์ย่อยดังนั้นการเปลี่ยนแปลงด้วยตนเองในค่าของจะไม่ทำให้เกิดสภาพแวดล้อมเชลล์ (พาเรนต์) ปัจจุบัน แน่นอนว่าcommandคำหลักอยู่ที่นั่นเพื่อป้องกันไม่ให้ฟังก์ชั่นเรียกตนเอง (ซึ่งจะส่งผลให้เกิดการวนซ้ำแบบวนซ้ำไม่สิ้นสุด) และแน่นอนคุณควรใส่คำจำกัดความเหล่านี้ลงใน.bashrcไฟล์การเริ่มต้นเชลล์ของคุณหรือ


มีความไร้ประสิทธิภาพเล็กน้อยในข้างต้น ในบางเชลล์ (ทุบตีเป็นหนึ่ง) ถ้าคุณพูด

( cmd 1 ;  cmd 2 ;;  cmd n )

โดยที่เป็นโปรแกรมภายนอกที่เรียกใช้งานได้ (เช่นไม่ใช่คำสั่งในตัว) เชลล์จะเก็บกระบวนการพิเศษไว้รอบ ๆ เพื่อรอให้ยุติ นี่คือ (arguably) ไม่จำเป็น ข้อดีและข้อเสียเป็นที่ถกเถียงกัน หากคุณไม่คิดที่จะผูกหน่วยความจำสักเล็กน้อยและสล็อตกระบวนการ (และการเห็นกระบวนการเชลล์มากกว่าที่คุณต้องการเมื่อคุณทำ) ให้ทำข้างบนและข้ามไปยังส่วนถัดไป เหมือนกันถ้าคุณใช้เปลือกหอยที่ไม่ทำให้กระบวนการพิเศษอยู่รอบตัว แต่ถ้าคุณต้องการหลีกเลี่ยงกระบวนการพิเศษสิ่งแรกที่ควรลองคือcmdncmdnps

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

execคำสั่งจะมีการป้องกันไม่ให้กระบวนการเปลือกพิเศษจากเอ้อระเหย

แต่ก็มี gotcha การจัดการของ shell SHLVLนั้นค่อนข้างใช้งานง่าย: เมื่อเชลล์เริ่มทำงานเชลล์จะตรวจสอบว่าSHLVLมีการตั้งค่าไว้หรือไม่ หากยังไม่ได้ตั้งค่า (หรือตั้งค่าเป็นอย่างอื่นที่ไม่ใช่ตัวเลข) เชลล์จะตั้งค่าเป็น 1 หากตั้งค่าไว้ (เป็นตัวเลข) เชลล์จะเพิ่ม 1 ลงไป

แต่ด้วยเหตุผลนี้ถ้าคุณบอกว่าexec shคุณSHLVLควรขึ้นไป แต่นั่นเป็นสิ่งที่ไม่พึงประสงค์เนื่องจากระดับกระสุนจริงของคุณไม่เพิ่มขึ้น เชลล์จัดการสิ่งนี้โดยการลบออกจาก 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éphane Chazelas (ใครจะรู้ทุกอย่าง)กระสุนบางตัวนั้นฉลาดพอที่จะไม่ทำสิ่งนี้ถ้าexecมันอยู่ใน subshell

ในการแก้ไขปัญหานี้คุณต้องทำ

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

จากนั้นฉันเห็นว่าคุณต้องการนับvimระดับ อิสระจากระดับเปลือก ทีนี้เคล็ดลับเดียวกันนี้ใช้ได้ผลดี (ด้วยการดัดแปลงเล็กน้อย):

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

(และอื่น ๆ สำหรับvi, viewฯลฯ ) 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เมื่อคุณสามารถทำสิ่งนี้กับเชลล์บิวด์อิน
สกอตต์

คำตอบนี้สมบูรณ์แบบ ฉันทำเครื่องหมายว่านี่เป็นวิธีแก้ปัญหา (น่าเสียดายที่ยังไม่มีคะแนนจำนวนมาก)
Pranay

วิธีการของคุณน่าทึ่งและคุณใช้เฉพาะโปรแกรมดั้งเดิมซึ่งหมายความว่ารวมถึงสิ่งนี้ใน. profile / .shellrc ของฉันจะไม่ทำลายอะไรเลย ฉันดึงสิ่งเหล่านั้นบนเครื่องที่ฉันทำงานอยู่
Pranay

1
โปรดทราบว่าdashมีการขยายเลขคณิต SHELL_LEVEL=$((SHELL_LEVEL + 1))ควรเพียงพอแม้ว่า $ SHELL_LEVEL ก่อนหน้านี้จะไม่มีการตั้งค่าหรือว่างเปล่า มันเป็นเพียงถ้าคุณมีที่จะพกพาไปที่บอร์นเชลล์ที่คุณจะต้องหันไปexprแต่แล้วคุณยังจะต้องเปลี่ยนด้วย$(...) `..`SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
Stéphane Chazelas

2
@Pranay มันไม่น่าจะมีปัญหา หากผู้โจมตีสามารถฉีดใด ๆ var env พลแล้วสิ่งที่ต้องการเส้นทาง / LD_PRELOAD เป็นทางเลือกที่ชัดเจนมากขึ้น แต่ถ้าไม่ใช่ตัวแปรที่มีปัญหาได้รับผ่านเช่นเดียวกับการกำหนดค่าโดยไม่ต้อง sudo reset_env (และใครสามารถบังคับให้bashสคริปต์เพื่ออ่าน ~ / .bashrc โดย ทำให้ stdin ซ็อกเก็ตเป็นต้น) จากนั้นอาจกลายเป็นปัญหา นั่นเป็นจำนวนมาก "ถ้า" s แต่สิ่งที่ต้องเก็บไว้ด้านหลังของจิตใจ (ข้อมูล unsanitized ในบริบททางคณิตศาสตร์เป็นสิ่งที่อันตราย)
Stéphane Chazelas

37

คุณสามารถนับจำนวนครั้งที่คุณต้องขึ้นไปตามลำดับกระบวนการจนกว่าคุณจะพบหัวหน้ากลุ่ม เช่นเดียวกับzshบน Linux:

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"
)

นั่นจะให้ 0 สำหรับเชลล์ที่เริ่มต้นโดยเทอร์มินัลอีมูเลเตอร์หรือ getty และอีกหนึ่งสำหรับการสืบทอด

คุณจะต้องทำเช่นนั้นเพียงครั้งเดียวเมื่อเริ่มต้น ตัวอย่างเช่น:

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

ในของคุณ~/.zshrcหรือเทียบเท่าที่จะมีมันในการแจ้งของคุณ

tcshและอีกหลายเปลือกหอยอื่น ๆ ( zsh, ksh93, fishและbashอย่างน้อย) รักษา$SHLVLตัวแปรที่พวกเขาเพิ่มขึ้นในการเริ่มต้น (และลดลงก่อนที่จะใช้คำสั่งอื่นด้วยexec(ยกเว้นกรณีที่execอยู่ใน subshell ถ้าพวกเขาไม่ได้รถ ( แต่จำนวนมาก))) ที่ติดตามจำนวนการทำรังของเชลล์เท่านั้นไม่ใช่การทำรัง นอกจากนี้ยังไม่รับประกันว่าระดับ 0 จะเป็นผู้นำเซสชัน


ใช่ .. สิ่งนี้หรือคล้ายกัน ฉันไม่ต้องการเขียนสิ่งนี้ด้วยตัวเองและฉันก็ไม่ได้ตั้งใจที่จะให้ใครเขียนสิ่งนี้ให้ฉัน . :( ผมก็หวังว่าสำหรับคุณลักษณะบางอย่างในกลุ่มหรือเปลือกหรือปลั๊กอินบางอย่างที่ถูกเก็บรักษาไว้อย่างสม่ำเสมอฉันค้นหา แต่ไม่พบอะไรบางอย่าง..
Pranay

31

echo $SHLVLใช้ ใช้หลักการ KISS สิ่งนี้อาจจะเพียงพอ


2
ทำงานให้แต่ไม่ได้สำหรับbash dash
agc

SHLVL ไม่ช่วยฉัน ฉันรู้เกี่ยวกับมันและมันก็เกิดขึ้นในการค้นหาเมื่อฉันค้นหา :) มีรายละเอียดเพิ่มเติมในคำถาม
Pranay

@Pranay คุณเป็นกลุ่มแน่นอนว่าไม่ได้ให้ข้อมูลนี้?
2497

@ user2497 ฉันค่อนข้าง นี่คือหลักฐานของคำถาม ฉันค้นหาทุกที่ฉันก็รับรู้ถึง SHLVL เช่นกัน ฉันต้องการ -> a) ให้แน่ใจว่าไม่มีสิ่งนั้น b) ทำมันด้วยจำนวนการพึ่งพา / บำรุงรักษาน้อยที่สุด
Pranay

16

pstreeหนึ่งในวิธีการแก้ปัญหาที่อาจเกิดขึ้นคือการมองไปที่การส่งออกของ เมื่อวิ่งเข้าไปในเปลือกที่เกิดจากภายในviส่วนของต้นไม้ที่แสดงรายการpstreeควรแสดงให้คุณเห็นว่าคุณอยู่ลึกแค่ไหน ตัวอย่างเช่น:

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

ใช่นั่นคือสิ่งที่ฉันแนะนำเป็นวิธีแก้ปัญหาของฉัน (ในคำถาม) ฉันไม่ต้องการแยก pstree แม้ว่า :( นี่เป็นสิ่งที่ดีสำหรับการอ่านด้วยตนเองฉันคิดว่าการเขียนโปรแกรมเพื่อทำเพื่อฉันและแจ้งให้ฉันทราบฉันไม่ชอบเขียน parser มากถ้าปลั๊กอิน / เครื่องมือทำแล้ว :)
Pranay

11

ตัวแปรแรก - ความลึกของเปลือกเท่านั้น

วิธีง่ายๆสำหรับbash: เพิ่มไปยัง.bashrcสองบรรทัดถัดไป (หรือเปลี่ยนPS1ค่าปัจจุบันของคุณ):

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

ผลลัพธ์:

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

หมายเลขที่จุดเริ่มต้นของสตริงพรอมต์จะแสดงถึงระดับเปลือก

ตัวแปรที่สองที่มีระดับเป็นกลุ่มและระดับเชลล์ทั้งคู่

เพิ่มบรรทัดนี้ไปที่ .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 - ระดับความลึกของเปลือก


นี้จะให้ฉันทำรังทุบตี มันจะไม่ให้รังที่เป็นกลุ่มกับฉัน :)
Pranay

@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 ของเชลล์ปัจจุบัน

ใช่ฉันกำลังทำสิ่งนี้สวยมาก ฉันยังมีบางอย่างที่จะนับ "ทุบตี" และ "เสียงเรียกเข้า" เป็นต้นฉันไม่ต้องการรักษามัน นอกจากนี้ยังเป็นไปไม่ได้ที่จะมีฟังก์ชั่นที่กำหนดเองจำนวนมากเมื่อคุณต้องสลับไปใช้ VM จำนวนมากและพัฒนาในบางครั้ง
Pranay

3

สิ่งนี้ไม่ได้ตอบคำถามอย่างเคร่งครัดแต่ในหลายกรณีอาจทำให้ไม่จำเป็นในการทำเช่นนั้น:

set -o ignoreeofเมื่อคุณเปิดเปลือกของคุณเรียก อย่า~/.bashrcใส่ไว้ในของคุณ

ทำให้เป็นนิสัยในการพิมพ์ Ctrl-D เมื่อคุณคิดว่าคุณอยู่ในเชลล์ระดับบนสุดและต้องการให้แน่ใจ

หากคุณไม่ได้อยู่ในเชลล์ระดับบนสุด Ctrl-D จะส่งสัญญาณ "end of input" ไปยังเชลล์ปัจจุบันและคุณจะถอยกลับไปหนึ่งระดับ

หากคุณอยู่ในระดับสูงสุดคุณจะได้รับข้อความ:

Use "logout" to leave the shell.

ฉันใช้สิ่งนี้ตลอดเวลาสำหรับเซสชัน SSH ที่ถูกผูกมัดเพื่อให้ง่ายต่อการเลื่อนกลับไปที่ระดับเฉพาะของห่วงโซ่ SSH มันใช้งานได้กับเชลล์ที่ซ้อนกันเช่นกัน


1
การทำเช่นนี้ช่วยได้อย่างแน่นอนและใช่มันจะช่วยขจัดความยุ่งยากมากมาย :) ฉันอาจรวมกับคำตอบที่ยอมรับ :)) ตั้งค่าตามเงื่อนไขดังนั้นฉันอาจไม่ต้องดูพรอมต์ตลอดเวลา
Pranay
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.