Bagaimana cara mendapatkan baris tertentu _ dan_ baris pertama file?


76

Dengan asumsi grep sederhana seperti:

$ psa aux | grep someApp
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

Ini memberikan banyak informasi, tetapi karena baris pertama dari perintah ps tidak ada, tidak ada konteks untuk info tersebut. Saya lebih suka bahwa baris pertama ps ditampilkan juga:

$ psa aux | someMagic someApp
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

Tentu saja, saya bisa menambahkan regex ke grep khusus untuk ps:

$ ps aux | grep -E "COMMAND|someApp"

Namun, saya lebih suka solusi yang lebih umum karena ada kasus lain di mana saya ingin memiliki baris pertama juga.

Sepertinya ini akan menjadi kasus penggunaan yang baik untuk deskriptor file "stdmeta" .


9
Kompleksitas yang diperlukan oleh jawaban-jawaban ini menunjukkan bagaimana filosofi Unix "melakukan satu hal dan melakukannya dengan baik" kadang-kadang gagal ketika kita diukur dengan tongkat pengukur kegunaan: mengetahui semua perintah ini cukup baik untuk menerapkannya pada masalah umum ini (memfilter info proses dan masih melihat label kolom) menunjukkan sisi buruk dari pendekatan: kadang-kadang hal-hal tidak cocok bersama dengan sangat bersih. Inilah sebabnya mengapa alat-alat seperti acksangat berguna, dan mengapa perlmeroket masa lalu sed, awkdll dalam popularitas: penting untuk bagian-bagian untuk meringkas menjadi satu kesatuan yang koheren.
iconoclast

3
tentu saja, untuk contoh khusus ini, Anda dapat menggunakan -Cargumen untuk psdan Anda tidak perlu memasukkannya ke grep. misalnya ps u -C someAppatau bahkanps u -C app1 -C app2 -C app3
cas

1
@iconoclast: tentu saja solusi Unixy akan menjadi alat yang dapat melipatgandakan beberapa baris masing-masing untuk disaring melalui set filter yang berbeda. Agak versi umum yang ps aux | { head -1; grep foo; }disebutkan oleh @Nahuel Fouilleul di bawah ini (mungkin ini satu-satunya solusi yang bisa saya ingat di tempat jika diperlukan)
Lie Ryan

@iconoclast: Kurang pengalaman, dan pengetahuan tentang alat, apa yang dilakukan alat dengan baik akan selalu tampak sama sekali tidak berguna. Mengetahui perintah dengan baik tidak ada artinya di halaman tongkat kegunaan, itu di halaman tongkat membaca manual dan praktek yang baik. Alat-alat ini telah ada selama beberapa dekade. Mereka bekerja dan cocok bersama dengan sangat baik (dan bersih).
Ярослав Рахматуллин

@ ЯрославРахматуллин: Saya pikir Anda mungkin telah salah mengerti apa yang saya katakan. (Mungkin karena bahasa Inggris bukan bahasa pertama Anda?) "Kegunaan" terkait dengan UX ("pengalaman pengguna") bukan utilitas (atau "kegunaan"). Menunjukkan bahwa ketika operasi sederhana adalah kompleks ini menyakiti kegunaan TIDAK TIDAK sama dengan mengatakan alat tidak berguna. Jelas mereka tidak berguna. Tidak ada orang yang waras yang akan mengatakan bahwa mereka tidak berguna.
iconoclast

Jawaban:


67

Cara yang baik

Biasanya Anda tidak dapat melakukan ini dengan grep tetapi Anda dapat menggunakan alat lain. AWK sudah disebutkan tetapi Anda juga dapat menggunakan sed, seperti ini:

sed -e '1p' -e '/youpattern/!d'

Bagaimana itu bekerja:

  1. Utilitas sed bekerja pada setiap baris secara individual, menjalankan perintah yang ditentukan pada masing-masing baris Anda dapat memiliki banyak perintah, menentukan beberapa -eopsi. Kami dapat menambahkan setiap perintah dengan parameter rentang yang menentukan apakah perintah ini harus diterapkan ke baris tertentu atau tidak.

  2. "1p" adalah perintah pertama. Ini menggunakan pperintah yang biasanya mencetak semua baris. Tapi kami menambahkannya dengan nilai numerik yang menentukan rentang yang harus diterapkan. Di sini, kami menggunakan 1yang berarti baris pertama. Jika Anda ingin mencetak lebih banyak baris, Anda dapat menggunakan di x,ypmana xbaris pertama untuk dicetak, yadalah baris terakhir untuk mencetak. Misalnya untuk mencetak 3 baris pertama, Anda akan menggunakan1,3p

  3. Perintah selanjutnya adalah dyang biasanya menghapus semua baris dari buffer. Sebelum perintah ini kita menempatkan yourpatternantara dua /karakter. Ini adalah cara lain (pertama adalah untuk menentukan baris mana seperti yang kita lakukan dengan pperintah) dari pengalamatan baris yang harus dijalankan oleh perintah. Ini berarti perintah hanya akan bekerja untuk garis yang cocok yourpattern. Kecuali, kita menggunakan !karakter sebelum dperintah yang membalikkan logikanya. Jadi sekarang akan menghapus semua garis yang tidak cocok dengan pola yang ditentukan.

  4. Pada akhirnya, sed akan mencetak semua garis yang tersisa di buffer. Tapi kami menghapus garis yang tidak cocok dari buffer sehingga hanya garis yang cocok yang akan dicetak.

Singkatnya: kami mencetak baris ke-1, lalu kami menghapus semua baris yang tidak cocok dengan pola kami dari input. Sisa baris dicetak (sehingga hanya baris yang melakukan sesuai pola).

Masalah lini pertama

Seperti yang disebutkan dalam komentar, ada masalah dengan pendekatan ini. Jika pola yang ditentukan cocok dengan baris pertama, maka akan dicetak dua kali (sekali dengan pperintah dan sekali karena kecocokan). Kita dapat menghindari ini dengan dua cara:

  1. Menambahkan 1dperintah setelah 1p. Seperti yang telah saya sebutkan, dperintah menghapus baris dari buffer dan kami menentukan rentangnya dengan nomor 1, yang berarti hanya akan menghapus baris ke-1. Jadi perintahnya adalahsed -e '1p' -e '1d' -e '/youpattern/!d'

  2. Menggunakan 1bperintah, alih-alih 1p. Itu tipuan. bperintah memungkinkan kita untuk beralih ke perintah lain yang ditentukan oleh label (dengan cara ini beberapa perintah dapat dihilangkan). Tetapi jika label ini tidak ditentukan (seperti dalam contoh kita) itu hanya melompat ke akhir perintah, mengabaikan sisa perintah untuk baris kita. Jadi dalam kasus kami, dperintah terakhir tidak akan menghapus baris ini dari buffer.

Contoh lengkap:

ps aux | sed -e '1b' -e '/syslog/!d'

Menggunakan titik koma

Beberapa sedimplementasi dapat menghemat Anda mengetik dengan menggunakan titik koma untuk memisahkan perintah daripada menggunakan beberapa -eopsi. Jadi jika Anda tidak peduli tentang menjadi portabel perintahnya akan ps aux | sed '1b;/syslog/!d'. Ini berfungsi setidaknya dalam GNU seddan busyboximplementasi.

Cara yang gila

Namun, inilah cara yang agak gila untuk melakukan ini dengan grep. Jelas tidak optimal, saya memposting ini hanya untuk tujuan belajar, tetapi Anda dapat menggunakannya misalnya, jika Anda tidak memiliki alat lain di sistem Anda:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog'

Bagaimana itu bekerja

  1. Pertama, kami menggunakan -nopsi untuk menambahkan nomor baris sebelum setiap baris. Kami ingin menghitung semua baris yang kami cocokkan .*- apa pun, bahkan baris kosong. Seperti yang disarankan dalam komentar, kami juga dapat mencocokkan '^', hasilnya sama.

  2. Kemudian kami menggunakan ekspresi reguler yang diperluas sehingga kami dapat menggunakan \|karakter khusus yang berfungsi sebagai OR. Jadi kami cocok jika garis dimulai dengan 1:(baris pertama) atau berisi pola kami (dalam hal ini adalah syslog).

Masalah nomor baris

Sekarang masalahnya adalah, kita mendapatkan nomor garis jelek ini di output kita. Jika ini merupakan masalah, kami dapat menghapusnya dengan cut, seperti ini:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog' | cut -d ':' -f2-

-dopsi menentukan pembatas, -fmenentukan bidang (atau kolom) yang ingin kita cetak. Jadi kami ingin memotong setiap baris pada setiap :karakter dan hanya mencetak 2 dan semua kolom berikutnya. Ini secara efektif menghapus kolom pertama dengan pembatasnya dan inilah yang kita butuhkan.


4
Penomoran baris dapat dilakukan dengan cat -nbaik dan akan terlihat lebih jelas karena grep disalahgunakan untuk ini.
Alfe

1
nltidak menghitung garis kosong (tetapi mencetaknya tanpa nomor garis), cat -nmemformat penomoran dengan spasi sebelumnya, grep -n .menghapus garis kosong sama sekali dan menambahkan tanda titik dua. Semua memiliki ... eh ... fitur ;-)
Alfe

2
Jawabannya ditulis dengan sangat mendidik. Saya mencoba mengganti "Berpura-pura" (Dekat awal) dengan "Prepend" untuk Anda tetapi ingin perubahan lebih lanjut dan saya tidak ingin mengubah omong kosong acak di pos Anda, jadi Anda mungkin ingin memperbaikinya.
Bill K

2
ps aux | sed '1p;/pattern/!d'akan mencetak baris pertama dua kali jika cocok dengan pola . Terbaik adalah dengan menggunakan bperintah: ps aux | sed -e 1b -e '/pattern/!d'. cat -nbukan POSIX. grep -n '^'akan memberi nomor setiap baris (bukan masalah untuk output ps yang tidak memiliki baris kosong). nl -ba -d $'\n'angka setiap baris.
Stéphane Chazelas

2
Catatan yang 1b;...tidak portabel atau POSIX, tidak ada perintah lain setelah "b", jadi Anda perlu baris baru atau ekspresi -e lainnya.
Stéphane Chazelas

58

Bagaimana perasaan Anda tentang menggunakan awkbukan grep?

chopper:~> ps aux | awk 'NR == 1 || /syslogd/'
USER              PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root               19   0.0  0.0  2518684   1160   ??  Ss   26Aug12   1:00.22 /usr/sbin/syslogd
mrb               574   0.0  0.0  2432852    696 s006  R+    8:04am   0:00.00 awk NR == 1 || /syslogd/
  • NR == 1: Jumlah record == 1; yaitu. baris pertama
  • ||: atau:
  • /syslogd/: Pola untuk dicari

Mungkin juga layak untuk dilihat pgrep, meskipun ini lebih untuk skrip daripada output yang dihadapi pengguna. Itu menghindari grepperintah itu sendiri muncul di output, meskipun.

chopper:~> pgrep -l syslogd
19 syslogd

Bagus sekali, terima kasih. Ini juga dapat digunakan untuk ekspansi di masa depan.
dotancohen

Saya perlu belajar saya beberapa awk. sangat bagus.
user606723

30
ps aux | { read line;echo "$line";grep someApp;}

Sunting: setelah komentar

ps aux | { head -1;grep someApp;}

Saya meskipun head -1akan membaca semua input, tetapi setelah mengujinya, ia bekerja juga.

{ head -1;grep ok;} <<END
this is a test
this line should be ok
not this one
END

output adalah

this is a test
this line should be ok

2
Itulah gagasan yang dijabarkan secara langsung dalam bash. Saya ingin memberikan lebih dari satu acungan jempol untuk ini. Saya mungkin hanya menggunakan { IFS='' read line; ... }kalau-kalau header dimulai dengan spasi.
Alfe

Ini benar- benar menyerang masalah secara langsung. Bagus!
dotancohen

3
Saya hanya menggunakan head -1alih-alih membaca / gema.
chepner

1
Ya, itu berfungsi dengan baik head -n1pada bash saya. Ini mungkin spesifik implementasi. Kepala saya tidak membaca seluruh input dalam hal ini, hanya baris pertama, meninggalkan sisanya di buffer input.
Krzysztof Adamski

2
head -n1lebih pendek, tetapi muncul bahkan spec POSIX diam untuk berapa banyak inputnya diizinkan untuk membaca, jadi mungkin read line; echo $linelebih portabel setelah semua.
chepner

14

Ps mendukung filter internal,

Misalkan Anda mencari proses bash:

ps -C bash -f

Akan mencantumkan semua proses yang bernama bash.


Terima kasih, itu baik untuk diketahui. Namun, tidak akan menemukan skrip mulai dari python, antara lain.
dotancohen

6

Saya cenderung mengirim tajuk ke stderr :

ps | (IFS= read -r HEADER; echo "$HEADER" >&2; cat) | grep ps

Ini biasanya cukup untuk keperluan membaca manusia. misalnya:

  PID TTY          TIME CMD
 4738 pts/0    00:00:00 ps

Bagian yang dikurung bisa masuk ke skrip sendiri untuk penggunaan umum.

Ada kenyamanan tambahan bahwa output dapat disalurkan lebih lanjut (ke sortdll.) Dan header akan tetap di atas.


5

Anda juga bisa menggunakan teedan head:

ps aux | tee >(head -n1) | grep syslog

Namun perlu dicatat bahwa selama teetidak dapat mengabaikan SIGPIPEsinyal (lihat misalnya diskusi di sini ), pendekatan ini membutuhkan solusi yang dapat diandalkan. Solusinya adalah dengan mengabaikan sinyal SIGPIPE, ini misalnya dapat dilakukan seperti ini di shell seperti bash:

trap '' PIPE    # ignore SIGPIPE
ps aux | tee >(head -n1) 2> /dev/null | grep syslog
trap - PIPE     # restore SIGPIPE handling

Perhatikan juga bahwa urutan output tidak dijamin .


Saya tidak akan bergantung pada ini untuk bekerja, pertama kali saya menjalankannya (zsh) menghasilkan tajuk kolom di bawah hasil grep. Kedua kalinya itu baik-baik saja.
Rqomey

1
Saya belum melihat ini, tapi salah satu cara untuk meningkatkan keandalan adalah untuk menyisipkan penundaan kecil di pipa sebelum grep: | { sleep .5; cat }.
Thor

2
Menambahkan tidur untuk menghindari masalah konkurensi selalu merupakan peretasan. Meskipun ini mungkin berhasil, ini adalah langkah menuju sisi gelap. -1 untuk ini.
Alfe

1
Saya punya beberapa masalah aneh lainnya ketika mencoba jawaban ini, saya membuat pertanyaan untuk memeriksa
Rqomey

Ini adalah penggunaan tee yang menarik, tetapi saya merasa itu tidak dapat diandalkan dan seringkali hanya mencetak garis output, tetapi bukan baris header.
dotancohen

4

Mungkin dua psperintah akan lebih mudah.

$ ps aux | head -1 && ps aux | grep someApp
USER             PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
100         3304   0.0  0.2  2466308   6476   ??  Ss    2Sep12   0:01.75 /usr/bin/someApp

2
Saya tidak suka solusi ini, terutama karena situasinya dapat berubah antara ps auxpanggilan pertama dan kedua ... Dan jika Anda hanya ingin baris pertama yang statis, mengapa tidak mengulanginya secara manual?
Shadur

1
Perubahan di antara kedua panggilan tidak perlu diganggu dalam situasi ini . Yang pertama hanya akan memberikan informasi utama yang akan selalu sesuai dengan output yang kedua.
Alfe

2
Saya tidak melihat mengapa ini diturunkan, itu jelas merupakan opsi yang layak. Upvoting.
dotancohen

4

Anda bisa menggunakan pidstat dengan:

pidstat -C someApp
or
pidstat -p <PID>

Contoh:

# pidstat -C java
Linux 3.0.26-0.7-default (hostname)    09/12/12        _x86_64_

13:41:21          PID    %usr %system  %guest    %CPU   CPU  Command
13:41:21         3671    0.07    0.02    0.00    0.09     1  java

Info Lebih Lanjut: http://linux.die.net/man/1/pidstat


Terima kasih, itu baik untuk diketahui. Namun, tidak akan menemukan skrip mulai dari python, antara lain.
dotancohen

4

Masukkan yang berikut ini di file .bashrc Anda atau salin / tempel ke dalam shell terlebih dahulu, untuk pengujian.

function psls { 
ps aux|head -1 && ps aux|grep "$1"|grep -v grep;
}

Penggunaan: psls [pola grep]

$ psls someApp
USER             PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root              21   0.0  0.0  2467312   1116   ??  Ss   Tue07PM   0:00.17 /sbin/someApp

Pastikan untuk mencari .bashrc Anda (atau .bash_profile jika Anda meletakkannya di sana):

source ~/.bashrc

Fungsi ini bahkan akan selesai secara otomatis pada baris perintah shell. Seperti yang Anda nyatakan dalam jawaban lain, Anda bisa menyalurkan baris pertama ke file untuk menyimpan satu panggilan ke ps.


1
Bagus, saya sudah menggunakan fungsi semacam itu selama bertahun-tahun. Saya memanggil versi sayapsl , yang hanya memanggil psdan grepsekali saja (dan tidak perlu head).
Adam Katz

3

urutkan tetapi pertahankan baris tajuk di bagian atas

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

Dan gunakan seperti ini

$ ps aux | body grep someApp
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

Terima kasih, beberapa jawaban itu membahas kasus umum pertanyaan ini. Sempurna!
dotancohen

3

Terima kasih banyak kepada Janis Papanagnou di comp.unix.shell, saya menggunakan fungsi berikut:

function grep1 {
    IFS= read -r header && printf "%s\n" "$header"; grep "$@"
}

Ini memiliki sejumlah keunggulan:

  • Bekerja dengan bash, zsh, dan mungkin ksh
  • Ini adalah pengganti drop-in untuk grep, sehingga Anda dapat terus menggunakan flag mana pun yang Anda inginkan: -iuntuk pencocokan case-sensitive, -Euntuk regex yang diperluas, dll.
  • Selalu menghasilkan kode keluar yang sama dengan grep, jika Anda ingin secara terprogram menentukan apakah ada garis yang benar-benar cocok
  • Tidak mencetak apa pun jika inputnya kosong

Contoh penggunaan:

$ ps -rcA | grep1 databases
  PID TTY           TIME CMD

$ ps -rcA | grep1 -i databases
  PID TTY           TIME CMD
62891 ??         0:00.33 com.apple.WebKit.Databases

2

Cara lain dengan gnu ed:

ed -s '!ps aux' <<< $'2,$v/PATTERN/d\n,p\nq\n'

atau, jika shell mendukung substitusi proses:

printf '%s\n' '2,$v/PATTERN/d' ,p q | ed -s <(ps aux)

itu adalah:

2,$v/PATTERN/d  - remove all lines not matching pattern (ignore the header)
,p              - print the remaining lines
q               - quit

Lebih portabel, tanpa gnu '!' atau substitusi shell - hanya menggunakan edbuilt-in runtuk rmenghasilkan output ps auxke buffer dan kemudian menghapus garis yang tidak cocok dalam 2,$kisaran dan mencetak hasilnya:

printf '%s\n' 'r !ps aux' '2,$v/PATTERN/d' ,p q | ed -s

Dan karena sedperintah dalam jawaban yang diterima output juga baris yang cocok dengan diri mereka sendiri, dengan sedyang mendukung -f-dan shell yang mendukung proses substitusi saya akan menjalankan:

printf '%s\n' '2,${' '/PATTERN/!d' '}' | sed -f - <(ps aux)

yang cukup banyak melakukan hal yang sama dengan edperintah sebelumnya .


1

Cara Perl:

ps aux | perl -ne 'print if /pattern/ || $.==1'

Cara membaca lebih mudah daripada sed, lebih cepat, tidak ada risiko untuk memilih kalimat yang tidak diinginkan.



0

Jika itu hanya untuk proses grepping dengan header penuh, saya akan memperluas saran @ mrb:

$ ps -f -p $(pgrep bash)
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
nasha     2810  2771  0  2014 pts/6    Ss+    0:00 bash
...

pgrep bash | xargs ps -fpakan mendapatkan hasil yang sama tetapi tanpa subkulit. Jika pemformatan lain diperlukan:

$ pgrep bash | xargs ps fo uid,pid,stime,cmd -p
  UID   PID STIME CMD
    0  3599  2014 -bash
 1000  3286  2014 /bin/bash
 ...

-2

Jika Anda tahu angka garis pastinya, mudah dengan perl! Jika Anda ingin mendapatkan baris 1 dan 5 dari file, katakan / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,5]){print}}' < /etc/passwd

Jika Anda ingin mendapatkan baris lain juga, cukup tambahkan angka mereka di array.


1
Terima kasih. Sesuai OP, saya tahu beberapa teks di baris, tetapi bukan nomor baris.
dotancohen

Ini muncul sebagai jawaban di Google ketika mencari kasus penggunaan ini terkait erat dengan OP, jadi perlu dicatat di sini.
Dagelf

1
Jika itu masalahnya, maka saya sangat menyarankan Anda memulai pertanyaan baru dan menjawabnya dengan jawaban ini. Tidak apa-apa untuk menjawab pertanyaan Anda sendiri di SE, terutama dalam situasi yang Anda sebutkan. Silakan dan tautkan ke pertanyaan baru Anda dalam komentar di OP.
dotancohen

Ada beberapa pertanyaan seperti itu, tetapi saat ini tidak muncul di Google.
Dagelf

Dagelf, intinya adalah - jawaban Anda tidak menjawab pertanyaan di sini. @dotancohen benar - jika ini muncul sebagai jawaban di Google ketika mencari kasus penggunaan ini terkait erat dengan OP kemudian ajukan pertanyaan terpisah - merinci kasus penggunaan terkait erat - dan menjawabnya.
don_crissti
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.