แยกผลลัพธ์ของคำสั่งตามคอลัมน์โดยใช้ Bash?


88

ฉันต้องการทำสิ่งนี้:

  1. เรียกใช้คำสั่ง
  2. จับเอาท์พุท
  3. เลือกบรรทัด
  4. เลือกคอลัมน์ของบรรทัดนั้น

ตัวอย่างเช่นสมมติว่าฉันต้องการรับชื่อคำสั่งจาก a $PID(โปรดทราบว่านี่เป็นเพียงตัวอย่างฉันไม่ได้แนะนำว่านี่เป็นวิธีที่ง่ายที่สุดในการรับชื่อคำสั่งจากรหัสกระบวนการ - ปัญหาจริงของฉันคือ คำสั่งอื่นที่มีรูปแบบผลลัพธ์ที่ฉันไม่สามารถควบคุมได้)

ถ้าฉันวิ่งpsฉันจะได้รับ:


  PID TTY          TIME CMD
11383 pts/1    00:00:00 bash
11771 pts/1    00:00:00 ps

ตอนนี้ฉันทำps | egrep 11383และได้รับ

11383 pts/1    00:00:00 bash

ขั้นตอนต่อไป: ps | egrep 11383 | cut -d" " -f 4. ผลลัพธ์คือ:

<absolutely nothing/>

ปัญหาคือcutตัดเอาต์พุตด้วยช่องว่างเดียวและเมื่อpsเพิ่มช่องว่างระหว่างคอลัมน์ที่ 2 และ 3 เพื่อรักษาความคล้ายคลึงของตารางให้cutเลือกสตริงว่าง แน่นอนฉันสามารถใช้cutเพื่อเลือกฟิลด์ที่ 7 และไม่ใช่ฟิลด์ที่ 4 แต่ฉันจะรู้ได้อย่างไรโดยเฉพาะเมื่อเอาต์พุตเป็นตัวแปรและไม่ทราบล่วงหน้า


2
ใช้ awk (และอีก 25 อักขระ)
Michael Foukarakis

คำตอบ:


179

วิธีง่ายๆวิธีหนึ่งคือเพิ่มพาสtrเพื่อบีบตัวคั่นฟิลด์ที่ซ้ำ ๆ กันออก:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4

1
ฉันชอบอันนี้ดูเหมือนว่าtrจะเบากว่าawk
flybywire

3
ฉันมักจะเห็นด้วย แต่นั่นอาจเป็นเพราะฉันยังไม่ได้เรียนรู้เรื่อง Awk :)
คลาย

จะไม่ทำงานถ้าคุณมีกระบวนการกับ PID ที่มี PID ที่คุณสนใจเป็นสตริงย่อย
David Grayson

1
นอกจากนี้แม่ชีเขตข้อมูลจะถูกปิดหาก PID: s บางตัวมีช่องว่างด้านซ้ายในขณะที่คนอื่นไม่อยู่
tripleee

68

ผมคิดว่าวิธีที่ง่ายที่สุดคือการใช้awk ตัวอย่าง:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash

4
เข้ากันได้กับคำถามเดิมps | awk "\$1==$PID{print\$4}"หรือ ps | awk -v"PID=$PID" '$1=PID{print$4}'(ดีกว่า) แน่นอนบน Linux คุณสามารถทำได้xargs -0n1 </proc/$PID/cmdline | head -n1หรือreadlink /proc/$PID/exeแต่อย่างไรก็ตาม ...
ephemient

เป็น;ใน{ print $4; }ต้อง? การลบดูเหมือนจะไม่มีผลกับฉันใน Linux เพียงแค่อยากรู้ว่ามันมีจุดประสงค์อะไร
igniteflow

@igniteflow จะไม่ระบุจุดสิ้นสุดของคำสั่งถ้าคุณต้องการเพิ่มต่อไปเมื่อผ่านคำสั่งพิมพ์?
joshmcode

16

โปรดทราบว่าtr -s ' 'ตัวเลือกนี้จะไม่ลบช่องว่างนำหน้าใด ๆ หากคอลัมน์ของคุณจัดชิดขวา (เช่นเดียวกับpspid) ...

$ ps h -o pid,user -C ssh,sshd | tr -s " "
 1543 root
19645 root
19731 root

จากนั้นการตัดจะทำให้เกิดบรรทัดว่างสำหรับบางฟิลด์หากเป็นคอลัมน์แรก:

$ <previous command> | cut -d ' ' -f1

19645
19731

เห็นได้ชัดว่าเว้นแต่คุณจะนำหน้าด้วยช่องว่าง

$ <command> | sed -e "s/.*/ &/" | tr -s " "

ตอนนี้สำหรับกรณีเฉพาะของหมายเลข pid (ไม่ใช่ชื่อ) มีฟังก์ชันที่เรียกว่าpgrep:

$ pgrep ssh


ฟังก์ชันเชลล์

อย่างไรก็ตามโดยทั่วไปแล้วยังคงเป็นไปได้ที่จะใช้ฟังก์ชันเชลล์ในลักษณะที่กระชับเนื่องจากมีสิ่งที่เป็นระเบียบเกี่ยวกับreadคำสั่ง:

$ <command> | while read a b; do echo $a; done

พารามิเตอร์ตัวแรกที่จะอ่านaจะเลือกคอลัมน์แรกและถ้ามีมากขึ้นทุกอย่างอื่นbจะถูกวางใน เป็นผลให้คุณไม่จำเป็นตัวแปรเกินกว่าจำนวนคอลัมน์ของคุณ1

ดังนั้น,

while read a b c d; do echo $c; done

จากนั้นจะแสดงคอลัมน์ที่ 3 ตามที่ระบุไว้ในความคิดเห็นของฉัน ...

การอ่านไปป์จะดำเนินการในสภาพแวดล้อมที่ไม่ส่งผ่านตัวแปรไปยังสคริปต์การเรียกใช้

out=$(ps whatever | { read a b c d; echo $c; })

arr=($(ps whatever | { read a b c d; echo $c $b; }))
echo ${arr[1]}     # will output 'b'`


โซลูชันอาร์เรย์

ดังนั้นเราจึงจบลงด้วยคำตอบโดย @frayser ซึ่งคือการใช้ตัวแปรเชลล์ IFS ซึ่งมีค่าเริ่มต้นเป็นช่องว่างเพื่อแยกสตริงออกเป็นอาร์เรย์ ใช้ได้เฉพาะใน Bash เท่านั้น Dash และ Ash ไม่รองรับ ฉันมีช่วงเวลาที่ยากลำบากในการแยกสตริงออกเป็นส่วนประกอบใน Busybox ง่ายพอที่จะรับองค์ประกอบเดียว (เช่นการใช้ awk) จากนั้นทำซ้ำสำหรับทุกพารามิเตอร์ที่คุณต้องการ แต่จากนั้นคุณจะเรียก awk ซ้ำ ๆ ในบรรทัดเดิมหรือใช้บล็อกการอ่านซ้ำที่มี echo ในบรรทัดเดียวกัน ซึ่งไม่มีประสิทธิภาพหรือสวย ดังนั้นคุณจึงแยกโดยใช้ ${name%% *}และอื่น ๆ ทำให้คุณโหยหาทักษะ Python เพราะจริงๆแล้วการเขียนสคริปต์เชลล์ไม่ใช่เรื่องสนุกอีกต่อไปหากฟีเจอร์ที่คุณคุ้นเคยครึ่งหนึ่งหรือมากกว่านั้นหายไป แต่คุณสามารถสันนิษฐานได้ว่าแม้แต่ python ก็ไม่ได้รับการติดตั้งในระบบดังกล่าวและมันก็ไม่ใช่ ;-)


คุณควรใช้เครื่องหมายคำพูดรอบตัวแปรในecho "$a"และecho "$c"แม้ว่า
tripleee

ดูเหมือนว่าทุกบล็อกที่ถูกดำเนินการในส่วนย่อยหรือกระบวนการของตัวเองและคุณไม่สามารถส่งคืนตัวแปรใด ๆ ไปยังบล็อกที่ปิดล้อมได้? แม้ว่าคุณจะได้รับผลลัพธ์หลังจากที่สะท้อนออกมา var=$(....... | { read a b c d; echo $c; }). ใช้งานได้กับสตริงเดียวเท่านั้นแม้ว่าใน Bash คุณสามารถแบ่งออกเป็นอาร์เรย์ได้โดยใช้ar=($var)
Xennex81

@tripleee ฉันไม่คิดว่านั่นเป็นปัญหาในขั้นตอนดังกล่าว คุณจะค้นพบในไม่ช้าว่าคุณต้องการสิ่งนั้นหรือไม่และหากสิ่งนั้นขาดหายไปในบางจุดนั่นคือบทเรียนการเรียนรู้ แล้วคุณจะรู้ว่าทำไมคุณต้องใช้เครื่องหมายคำพูดคู่นั้น ;-) แล้วมันก็ไม่ใช่สิ่งที่คุณเคยได้ยินจากคนอื่นพูดอีกต่อไป เล่นกับไฟ! : ง. : p.
Xennex81

คำตอบอย่างละเอียด: D
ncomputers

นี่เป็นคำตอบที่มีประโยชน์มากเกินไปสำหรับฉันที่จะไม่พูดเช่นนั้น
Ivan X

4

ลอง

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done

1
@flybywire - อาจจะมากเกินไปสำหรับตัวอย่างง่ายๆนี้ แต่สำนวนนี้ดีมากหากคุณต้องการประมวลผลที่ซับซ้อนมากขึ้นกับข้อมูลที่เลือก
James Anderson

นอกจากนี้โปรดทราบว่าทุกวันนี้เชลล์สคริปต์เริ่มต้นมักจะไม่ทุบตี
David Given


2

คล้ายกับโซลูชัน awk ของ brianegge นี่คือค่าเทียบเท่า Perl:

ps | egrep 11383 | perl -lane 'print $F[3]'

-aเปิดใช้งานโหมดแยกอัตโนมัติซึ่งจะเติม@Fอาร์เรย์ด้วยข้อมูลคอลัมน์
ใช้-F,หากข้อมูลของคุณคั่นด้วยจุลภาคแทนที่จะคั่นด้วยช่องว่าง

ฟิลด์ 3 ถูกพิมพ์เนื่องจาก Perl เริ่มนับจาก 0 แทนที่จะเป็น 1


1
ขอบคุณสำหรับโซลูชัน perl ของคุณ - ไม่รู้เกี่ยวกับ autosplit และยังคิดว่า perl เป็นเครื่องมือในการยุติเครื่องมืออื่น ๆ .. ;)
Gerard ONeill

1

รับบรรทัดที่ถูกต้อง (ตัวอย่างสำหรับบรรทัดที่ 6) ทำด้วย head และ tail และคำที่ถูกต้อง (คำที่ 4) สามารถจับได้ด้วย awk:

command|head -n 6|tail -n 1|awk '{print $4}'

เพียงแค่สังเกตสำหรับผู้อ่านในอนาคตว่า awk สามารถเลือกทีละบรรทัดได้เช่นกัน: awk NR=6 {print $4}จะมีประสิทธิภาพมากขึ้นเล็กน้อย
David Z

1
และแน่นอนว่าฉันหมายถึงawk NR==6 {print $4}* doh *
David Z

1

คำสั่งของคุณ

ps | egrep 11383 | cut -d" " -f 4

คิดถึงtr -sที่จะบีบช่องว่างเช่นผ่อนคลายอธิบายในคำตอบของเขา

อย่างไรก็ตามคุณอาจต้องการใช้awkเนื่องจากจัดการการกระทำเหล่านี้ทั้งหมดในคำสั่งเดียว:

ps | awk '/11383/ {print $4}'

นี้จะพิมพ์คอลัมน์ที่ 4 11383ในเส้นที่มี หากคุณต้องการให้สิ่งนี้ตรงกัน11383หากปรากฏในตอนต้นของบรรทัดคุณสามารถพูดps | awk '/^11383/ {print $4}'ได้


0

แทนที่จะทำ greps และสิ่งต่างๆเหล่านี้เราขอแนะนำให้คุณใช้ความสามารถ ps ในการเปลี่ยนรูปแบบผลลัพธ์

ps -o cmd= -p 12345

คุณได้รับบรรทัด cmmand ของกระบวนการที่ระบุ pid และไม่มีอะไรอื่น

สิ่งนี้เป็นไปตาม POSIX และอาจถือได้ว่าพกพาได้


1
flybywire ระบุว่าเขาใช้ ps เป็นตัวอย่างเท่านั้นคำถามนี้กว้างกว่านั้น
Ogre Psalm33

0

Bash setจะแยกวิเคราะห์ผลลัพธ์ทั้งหมดเป็นพารามิเตอร์ตำแหน่ง

ตัวอย่างเช่นด้วยset $(free -h)คำสั่งecho $7จะแสดง "Mem:"


วิธีนี้มีประโยชน์ก็ต่อเมื่อคำสั่งมีเอาต์พุตบรรทัดเดียว ทั่วไปไม่เพียงพอ
codeforester

นั่นไม่เป็นความจริงเอาต์พุตทั้งหมดจะถูกวางไว้ในพารามิเตอร์ตำแหน่งโดยไม่คำนึงถึงบรรทัด อดีตset $(sar -r 1 1); echo "${23}"
dman

ประเด็นของฉันคือยากที่จะกำหนดตำแหน่งของอาร์กิวเมนต์เมื่อเอาต์พุตมีขนาดใหญ่และมีหลายฟิลด์ awkเป็นวิธีที่ดีที่สุดในการดำเนินการนี้
codeforester

นี่เป็นเพียงอีกทางแก้ปัญหา OP อาจไม่ต้องการเรียนรู้ภาษา awk สำหรับกรณีการใช้งานเดียวนี้ แท็กทำรัฐและไม่ได้bash awk
dman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.