Apa yang dilakukan “xargs grep”?

25

Saya tahu grepperintah dan saya belajar tentang fungsi xargs, jadi saya membaca halaman ini yang memberikan beberapa contoh tentang cara menggunakan xargsperintah.

Saya bingung dengan contoh terakhir, contoh 10. Ia mengatakan "Perintah xargs mengeksekusi perintah grep untuk menemukan semua file (di antara file yang disediakan oleh perintah find) yang berisi string 'stdlib.h'"

$ find . -name '*.c' | xargs grep 'stdlib.h'
./tgsthreads.c:#include
./valgrind.c:#include
./direntry.c:#include
./xvirus.c:#include
./temp.c:#include
...
...
...

Namun, apa bedanya hanya menggunakan

$ find . -name '*.c' | grep 'stdlib.h'

?

Jelas, saya masih berjuang dengan apa yang sebenarnya dilakukan xargs, jadi bantuan apa pun dihargai!

Alpha Omega
sumber
2
Pertanyaan ini mungkin membantu: Kapan XArgs dibutuhkan?
TheOdd

Jawaban:

30
$ find . -name '*.c' | grep 'stdlib.h'

Ini mem-pipe output (stdout) * dari findke (stdin of) * grep 'stdlib.h' sebagai teks (mis. Nama file diperlakukan sebagai teks). grepmelakukan hal yang biasa dan menemukan baris yang cocok dalam teks ini (nama file apa pun yang mengandung pola). Isi file tidak pernah dibaca.

$ find . -name '*.c' | xargs grep 'stdlib.h'

Ini membangun sebuah perintah di grep 'stdlib.h' mana setiap hasil dari findadalah argumen - jadi ini akan mencari kecocokan di dalam setiap file yang ditemukan oleh find( xargsdapat dianggap sebagai mengubah stdin menjadi argumen ke perintah yang diberikan) *

Gunakan -type fdi perintah find Anda, atau Anda akan mendapatkan kesalahan dari grepdirektori yang cocok. Juga, jika nama file memiliki spasi, xargsakan gagal total, jadi gunakan pemisah nol dengan menambahkan -print0dan xargs -0untuk hasil yang lebih andal:

find . -type f -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

* menambahkan poin penjelasan tambahan ini seperti yang disarankan dalam komentar oleh @cat

Zanna
sumber
2
Anda mungkin mempertimbangkan mencatat (karena tampaknya Anda dihilangkan) titik kunci yang |pipa stdout ke grep ini stdin yang tidak sama dengan argumen grep dan memberikan hasil yang membingungkan.
kucing
1
Atau gunakan GNU find's find -name '*.c' -exec grep stdlib.h {} +. Saya sebenarnya tidak pernah menggunakan xargs. Juga terkejut bahwa tidak ada yang menyebutkan bahwa xargs memiliki tujuan yang mirip dengan grep $(find)perintah substitusi, jadi saya menulis jawaban saya sendiri. Menjelaskan xargs sebagai substitusi perintah dengan batasan dan masalah yang lebih sedikit tampak wajar.
Peter Cordes
Satu situasi yang saya gunakan pada xargs adalah jika saya menghapus banyak file sebagai akibat dari find. Jika Anda hanya melakukan -exec rm, itu akan menjalankan rm pada setiap file satu per satu, yang sangat tidak efisien. Perpipaan ke xargs akan melakukannya sekaligus dengan satu rm. Membatasi dengan mengatakan -n50 (lakukan 50 sekaligus) dapat mencegah baris perintah overflow (masalah dengan banyak file).
lsd
1
@ ls: Mengapa tidak find -deleteuntuk kasus khusus itu? Atau untuk perintah selain rm, jika Anda memiliki GNU find, maka -exec some_command {} +kelompokkan ke dalam batch seperti xargs, alih-alih \;perilaku menjalankan perintah secara terpisah untuk masing-masing.
Peter Cordes
@lsd findmenjalankan perintah pada setiap file jika dan hanya jika menggunakan -exec command \;Keduanya xargsdan -exec command \+akan memanggil perintah dengan jumlah argumen maksimum yang diizinkan oleh sistem. Dengan kata lain, mereka setara
Sergiy Kolodyazhnyy
6

xargs mengambil input standar dan mengubahnya menjadi baris perintah args.

find . -name '*.c' | xargs grep 'stdlib.h' sangat mirip dengan

grep 'stdlib.h' $(find . -name '*.c')  # UNSAFE, DON'T USE

Dan akan memberikan hasil yang sama selama daftar nama file tidak terlalu panjang untuk satu baris perintah. (Linux mendukung megabyte teks pada satu baris perintah, jadi biasanya Anda tidak perlu xargs.)


Tetapi keduanya menyebalkan, karena mereka rusak jika nama file Anda mengandung spasi . Sebaliknya, find -print0 | xargs -0bekerja, tetapi begitu juga

find . -name '*.c' -exec grep 'stdlib.h' {} +

Itu tidak pernah menyalurkan nama file di mana saja: findbatch mereka menjadi baris perintah besar dan berjalan grepsecara langsung.

\;bukannya +menjalankan grep secara terpisah untuk setiap file, yang jauh lebih lambat. Jangan lakukan itu. Tetapi +ini adalah ekstensi GNU, jadi Anda harus xargsmelakukan ini secara efisien jika Anda tidak dapat menganggap GNU find.


Jika Anda keluar xargs, find | grepapakah polanya cocok dengan daftar nama file yang finddicetak.

Jadi pada titik itu, sebaiknya Anda lakukan saja find -name stdlib.h. Tentu saja, dengan -name '*.c' -name stdlib.h, Anda tidak akan mendapatkan hasil apa pun karena pola-pola itu tidak bisa cocok, dan perilaku default find adalah untuk DAN aturan bersama.

Gantikan lesspada titik mana saja dalam proses untuk melihat output apa saja yang dihasilkan oleh bagian pipa.


Bacaan lebih lanjut: http://mywiki.wooledge.org/BashFAQ memiliki beberapa hal hebat.

Peter Cordes
sumber
1
GNU xargs juga harus -dmengatur pemisah, sehingga Anda dapat menggunakan -d'\n'untuk menangani daftar yang dipisahkan oleh baris baru, yang mungkin berguna jika Anda menangani daftar nama file dalam file, dll. (Selama nama file tidak memiliki baris baru di dalamnya, yaitu.)
ilkkachu
@ilkkachu: yeah, baris baru dalam nama file jauh lebih jarang daripada spasi, karena mereka melanggar sebagian besar skrip. myfunc(){ local IFS=$'\n'; fgrep stdlib.h` $ (find) ; }juga berfungsi dengan efek yang sama. Atau sebagai one-liner, sebuah (IFS=...; cmd...)subkulit juga berfungsi untuk memuat perubahan ke IFS tanpa harus menyimpan / mengembalikannya.
Peter Cordes
@PeterCordes Tolong jangan lakukan command $( find )jenis barang. Nama file bermasalah dengan spasi dan karakter khusus dapat merusak jenis hal ini. Paling tidak dua kali lipat kutipan substitusi perintah.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy: Terima kasih telah menunjukkan bahwa sepertinya saya benar-benar merekomendasikan melakukan itu. Orang yang membaca sekilas mungkin telah menyalin / menempel itu daripada membaca bagian selanjutnya. Diperbarui untuk mengatasinya.
Peter Cordes
@SergiyKolodyazhnyy: Atau apakah Anda membalas komentar saya? Perhatikan bahwa saya menyetel IFSjadi itu setara dengan menggunakan xargs '-d\n'. Ekspansi global dan pemrosesan metacharacter shell terjadi sebelum efek substitusi perintah, jadi saya pikir itu aman bahkan dengan nama file yang mengandung $()atau >. Setuju bahwa menggunakan pemisahan kata pada substitusi perintah bukanlah praktik yang baik kecuali untuk penggunaan interaktif satu kali di mana Anda mengetahui sesuatu tentang nama file. Tapi command "$(find)"ini hanya berguna jika Anda mengharapkannya menghasilkan tepat 1 nama file ...
Peter Cordes
5

Secara umum, xargsdigunakan untuk kasus-kasus di mana Anda akan mem-pipe (dengan simbol |) sesuatu dari satu perintah ke yang lain ( Command1 | Command2), tetapi output dari perintah pertama tidak diterima dengan benar sebagai input untuk perintah kedua.

Ini biasanya terjadi ketika perintah kedua tidak menangani input data melalui Standard In (stdin) dengan benar (misalnya: Beberapa baris sebagai input, cara pengaturan baris, karakter yang digunakan sebagai input, beberapa parameter sebagai input, tipe data yang diterima sebagai masukan, dll.). Untuk memberi Anda contoh cepat, uji berikut ini:

Contoh 1:

ls | echo- Ini tidak akan melakukan apa-apa karena echotidak tahu bagaimana menangani input yang diterimanya. Sekarang dalam kasus ini jika kita menggunakannya xargsakan memproses input dengan cara yang dapat ditangani dengan benar oleh echo(misalnya: Sebagai satu baris informasi)

ls | xargs echo- Ini akan menampilkan semua informasi dari lsdalam satu baris

Contoh 2:

Katakanlah saya memiliki beberapa file goLang di dalam folder bernama go. Saya akan mencari mereka dengan sesuatu seperti ini:

find go -name *.go -type f | echo- Tetapi jika simbol pipa ada dan echopada akhirnya, itu tidak akan berhasil.

find go -name *.go -type f | xargs echo- Ini akan berhasil berkat xargstetapi jika saya ingin setiap tanggapan dari findperintah dalam satu baris, saya akan melakukan yang berikut:

find go -name *.go -type f | xargs -0 echo- Dalam hal ini, output yang sama dari findakan ditunjukkan oleh echo.

Perintah seperti cp, echo, rm, lessdan lainnya yang membutuhkan cara yang lebih baik untuk menangani input mendapatkan manfaat saat digunakan bersama xargs.

Luis Alvarado
sumber
4

xargs digunakan untuk menghasilkan argumen baris perintah secara otomatis (biasanya) pada daftar file.

Jadi mempertimbangkan beberapa alternatif untuk menggunakan xargsperintah followoing :

find . -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

Ada beberapa alasan untuk menggunakannya alih-alih opsi lain yang awalnya tidak disebutkan dalam jawaban lain:

  1. find . -name '*.c' -exec grep 'stdlib.h' {}\;akan menghasilkan satu grepproses untuk setiap file — ini umumnya dianggap praktik yang buruk, dan dapat memberi beban besar pada sistem jika ada banyak file yang ditemukan.
  2. Jika ada banyak file, sebuah grep 'stdlib.h' $(find . -name '*.c')perintah kemungkinan akan gagal, karena output dari $(...)operasi akan melebihi panjang baris perintah maksimum dari shell

Seperti disebutkan dalam jawaban lain, alasan untuk menggunakan -print0argumen finddalam skenario ini dan -0argumen untuk xargs, adalah agar nama file dengan karakter tertentu (misalnya kutipan, spasi atau bahkan baris baru) masih ditangani dengan benar.

Michael Firth
sumber