Pisahkan keluaran perintah dengan kolom menggunakan Bash?

88

Aku ingin melakukan ini:

  1. jalankan perintah
  2. menangkap hasilnya
  3. pilih satu baris
  4. pilih kolom dari baris itu

Sebagai contoh, katakanlah saya ingin mendapatkan nama perintah dari a $PID (harap dicatat ini hanya sebuah contoh, saya tidak menyarankan ini adalah cara termudah untuk mendapatkan nama perintah dari id proses - masalah saya sebenarnya adalah dengan perintah lain yang format keluarannya tidak dapat saya kendalikan).

Jika saya menjalankan pssaya mendapatkan:


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

Sekarang saya lakukan ps | egrep 11383dan dapatkan

11383 pts/1    00:00:00 bash

Langkah berikutnya: ps | egrep 11383 | cut -d" " -f 4. Outputnya adalah:

<absolutely nothing/>

Masalahnya adalah cutmemotong output dengan spasi tunggal, dan saat psmenambahkan beberapa spasi antara kolom ke-2 dan ke-3 untuk menjaga beberapa kemiripan tabel, cutmengambil string kosong. Tentu saja, saya bisa menggunakan cutuntuk memilih bidang ke-7 dan bukan ke-4, tetapi bagaimana saya bisa tahu, khususnya ketika outputnya bervariasi dan tidak diketahui sebelumnya.

flybywire
sumber
2
Gunakan awk (dan 25 karakter lainnya).
Michael Foukarakis

Jawaban:

179

Salah satu cara mudah adalah dengan menambahkan pass of truntuk menekan pemisah bidang berulang apa pun:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
beristirahat
sumber
1
Saya suka yang ini, sepertinya trlebih ringan daripadaawk
flybywire
3
Saya cenderung setuju, tapi itu mungkin juga karena saya belum belajar awk. :)
bersantai
Tidak akan berfungsi jika Anda kebetulan memiliki proses dengan PID yang berisi PID yang Anda minati sebagai subtring.
David Grayson
1
Dan juga, field nunbers akan dimatikan jika beberapa PID: s memiliki ruang yang empuk di sebelah kiri sementara yang lain tidak.
tripleee
68

Menurut saya cara paling sederhana adalah menggunakan awk . Contoh:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash
brianegge.dll
sumber
4
Untuk kompatibilitas dengan pertanyaan asli, ps | awk "\$1==$PID{print\$4}"atau (lebih baik) ps | awk -v"PID=$PID" '$1=PID{print$4}'. Tentu saja, di Linux Anda dapat melakukannya xargs -0n1 </proc/$PID/cmdline | head -n1atau readlink /proc/$PID/exe, tetapi bagaimanapun ...
ephemient
Apakah ;di { print $4; }diperlukan? Menghapusnya tampaknya tidak berpengaruh bagi saya di Linux, hanya ingin tahu tujuannya
igniteflow
@ Signiteflow Bukankah itu menunjukkan akhir dari perintah jika Anda ingin terus menambahkan melewati pernyataan cetak?
joshmcode
16

Harap dicatat bahwa tr -s ' 'opsi tidak akan menghapus satu spasi pun di depan. Jika kolom Anda rata kanan (seperti pspid) ...

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

Kemudian pemotongan akan menghasilkan baris kosong untuk beberapa bidang tersebut jika itu adalah kolom pertama:

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

19645
19731

Kecuali jika Anda mendahului dengan spasi, tentu saja

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

Sekarang, untuk kasus nomor pid ini (bukan nama), ada fungsi yang disebut pgrep:

$ pgrep ssh


Fungsi shell

Namun, secara umum sebenarnya masih memungkinkan untuk menggunakan fungsi shell secara ringkas, karena ada hal yang rapi tentang readperintah tersebut:

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

Parameter pertama untuk dibaca,, amemilih kolom pertama, dan jika ada lebih banyak, yang lainnya akan dimasukkan b. Akibatnya, Anda tidak membutuhkan lebih banyak variabel daripada jumlah kolom +1 Anda .

Begitu,

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

kemudian akan menampilkan kolom ke-3. Seperti yang ditunjukkan dalam komentar saya ...

Pembacaan beralur akan dieksekusi di lingkungan yang tidak meneruskan variabel ke skrip pemanggil.

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'`


Solusi Array

Jadi kami kemudian berakhir dengan jawaban oleh @frayser yang menggunakan variabel shell IFS yang defaultnya ke spasi, untuk membagi string menjadi array. Ini hanya berfungsi di Bash. Dash dan Ash tidak mendukungnya. Saya mengalami kesulitan untuk membagi string menjadi beberapa komponen dalam Busybox. Cukup mudah untuk mendapatkan satu komponen (misalnya menggunakan awk) dan kemudian mengulanginya untuk setiap parameter yang Anda butuhkan. Tetapi kemudian Anda akhirnya berulang kali memanggil awk di baris yang sama, atau berulang kali menggunakan blok baca dengan gema di baris yang sama. Yang mana tidak efisien atau cantik. Jadi Anda akhirnya membelah menggunakan ${name%% *}dan seterusnya. Membuat Anda merindukan beberapa keterampilan Python karena sebenarnya skrip shell tidak lagi menyenangkan jika setengah atau lebih fitur yang biasa Anda gunakan, hilang. Tetapi Anda dapat berasumsi bahwa bahkan python tidak akan diinstal pada sistem seperti itu, dan itu bukan ;-).

Xennex81
sumber
Anda harus menggunakan tanda kutip di sekitar variabel dalam echo "$a"dan echo "$c".
tripleee
Tampaknya seolah-olah setiap blok pipih dieksekusi dalam subkulit atau prosesnya sendiri dan Anda tidak dapat mengembalikan variabel apa pun ke blok terlampir? Meskipun Anda bisa mendapatkan outputnya setelah menggemakannya. var=$(....... | { read a b c d; echo $c; }). Itu hanya berfungsi untuk satu (string), meskipun di Bash Anda dapat membaginya menjadi array menggunakanar=($var)
Xennex81
@tripleee Saya rasa itu bukan masalah pada tahap proses seperti itu. Anda akan segera mengetahui apakah Anda membutuhkannya atau tidak, dan jika itu rusak pada suatu saat, itu adalah pelajaran pembelajaran. Dan kemudian Anda tahu mengapa Anda harus menggunakan tanda kutip ganda ;-). Dan kemudian itu bukan lagi sesuatu yang Anda dengar dari orang lain. Bermain api! : D. : hal.
Xennex81
jawaban yang diuraikan: D
ncomputers
Ini adalah jawaban yang terlalu membantu bagi saya untuk tidak mengatakannya.
Ivan X
4

mencoba

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done
James Anderson
sumber
1
@flybywire - mungkin berlebihan untuk contoh sederhana ini, tetapi idiom ini sangat bagus jika Anda perlu melakukan pemrosesan yang lebih kompleks pada data yang dipilih.
James Anderson
Juga, ketahuilah bahwa akhir-akhir ini shell skrip default biasanya bukan bash.
David Diberikan
2

Menggunakan variabel array

set $(ps | egrep "^11383 "); echo $4

atau

A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
frayser
sumber
2

Mirip dengan solusi awk brianegge, berikut adalah padanan Perl:

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

-amengaktifkan mode autosplit, yang mengisi @Farray dengan data kolom.
Menggunakan-F, jika data Anda dipisahkan dengan koma, bukan dipisahkan spasi.

Bidang 3 dicetak karena Perl mulai menghitung dari 0 daripada 1

Chris Koknat
sumber
1
Terima kasih atas solusi perl Anda - tidak tahu tentang autosplit, dan masih menganggap perl adalah alat untuk mengakhiri alat lainnya ..;).
Gerard ONeill
1

Mendapatkan baris yang benar (contoh untuk baris no. 6) dilakukan dengan head dan tail dan kata yang benar (kata no. 4) dapat ditangkap dengan awk:

command|head -n 6|tail -n 1|awk '{print $4}'
Soulmerge
sumber
Hanya mencatat untuk pembaca mendatang bahwa awk juga dapat memilih berdasarkan baris: awk NR=6 {print $4}akan sedikit lebih efisien
David Z
1
dan tentu saja yang saya maksud adalah awk NR==6 {print $4}* doh *
David Z
1

Perintah Anda

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

merindukan tr -suntuk memeras spasi, seperti yang dijelaskan oleh relaksasi dalam jawabannya .

Namun, Anda mungkin ingin menggunakan awk, karena ini menangani semua tindakan ini dalam satu perintah:

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

Ini mencetak kolom ke-4 di baris yang berisi 11383. Jika Anda ingin ini cocok 11383jika muncul di awal baris, maka Anda bisa mengatakan ps | awk '/^11383/ {print $4}'.

fedorqui 'JADI berhenti merugikan'
sumber
0

Alih-alih melakukan semua grep dan hal-hal ini, saya menyarankan Anda untuk menggunakan kapabilitas ps untuk mengubah format output.

ps -o cmd= -p 12345

Anda mendapatkan baris perintah dari sebuah proses dengan pid yang ditentukan dan tidak ada yang lain.

Ini adalah konforman POSIX dan karenanya dapat dianggap portabel.

P Shved
sumber
1
flybywire menyatakan dia hanya menggunakan ps sebagai contoh, pertanyaannya lebih umum dari itu.
Ogre Mazmur33
0

Bash's set akan mengurai semua output menjadi parameter posisi.

Misalnya, dengan set $(free -h)perintah, echo $7akan muncul "Mem:"

dman
sumber
Metode ini berguna hanya jika perintah memiliki satu baris keluaran. Tidak cukup umum.
codeforester
Itu tidak benar, semua keluaran ditempatkan ke dalam parameter posisi tanpa memperhatikan garis. ex set $(sar -r 1 1); echo "${23}"
dman
Maksud saya adalah sulit untuk menentukan posisi argumen ketika outputnya banyak dan memiliki banyak bidang. awkadalah cara terbaik untuk melakukannya.
codeforester
Ini hanyalah solusi lain. OP mungkin tidak ingin mempelajari bahasa awk untuk kasus penggunaan tunggal ini. Tag menyatakan bashdan tidakawk .
dman