Buat xargs menangani nama file yang berisi spasi

252
$ ls *mp3 | xargs mplayer  

Playing Lemon.  
File not found: 'Lemon'  
Playing Tree.mp3.  
File not found: 'Tree.mp3'  

Exiting... (End of file)  

Perintah saya gagal karena file "Lemon Tree.mp3" berisi spasi dan xargs menganggapnya dua file. Dapatkah saya membuat + xargs berfungsi dengan nama file seperti ini?

showkey
sumber
Alih-alih ls |grep mp3 |sed -n "7p"Anda hanya bisa menggunakan echo "Lemon Tree.mp3".
Micha Wiedenmann
Pertanyaan ini juga dijawab oleh stackoverflow.com/a/33528111/94687
imz - Ivan Zakharyaschev

Jawaban:

256

The xargsperintah mengambil karakter putih ruang (tab, spasi, baris baru) sebagai pembatas. Anda dapat mempersempitnya hanya untuk karakter baris baru ('\ n') dengan -dopsi seperti ini:

ls *.mp3 | xargs -d '\n' mplayer

Ini hanya berfungsi dengan GNU xargs. Untuk sistem BSD, gunakan -0opsi seperti ini:

ls *.mp3 | xargs -0 mplayer

Metode ini lebih sederhana dan juga berfungsi dengan GNU xargs.

sinar
sumber
6
Jawaban terbaik untuk penggunaan umum! Ini berfungsi bahkan jika perintah Anda sebelumnya bukan "find"
nexayq
28
Sayangnya, opsi ini tidak tersedia di OS X.
Thomas Tempelmann
25
@ Thomas Untuk OS X, benderanya adalah -E, mis:xargs -E '\n'
30
Pada OS X, -E '\ n' tidak berpengaruh bagi saya, saya juga tidak mengharapkannya karena ia memodifikasi eofstr dan bukan pemisah rekaman. Namun, saya dapat menggunakan flag -0 sebagai solusi, bahkan jika perintah sebelumnya bukan 'find', dengan mensimulasikan efek flag -print0 find di input saya, mis .: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
biomiker
10
Untuk OS X, Anda dapat "Bir install findutils", yang memberikan Anda "gxargs" perintah yang tidak memiliki saklar -d.
Tom De Leu
213

Utilitas xargs membaca string yang dibatasi spasi, tab, baris baru dan akhir file dari input standar dan menjalankan utilitas dengan string sebagai argumen.

Anda ingin menghindari menggunakan ruang sebagai pembatas. Ini dapat dilakukan dengan mengubah pembatas untuk xargs. Menurut manual:

 -0      Change xargs to expect NUL (``\0'') characters as separators,
         instead of spaces and newlines.  This is expected to be used in
         concert with the -print0 function in find(1).

Seperti:

 find . -name "*.mp3" -print0 | xargs -0 mplayer

Untuk menjawab pertanyaan tentang memainkan mp3 ketujuh; lebih mudah dijalankan

 mplayer "$(ls *.mp3 | sed -n 7p)"
Jens
sumber
10
Ini menggunakan GNU finddan GNU xargs; tidak semua versi dari program-program tersebut mendukung opsi-opsi tersebut (walaupun ada kasus yang harus dibuat).
Jonathan Leffler
1
@JonathanLeffler s / GNU / FreeBSD / g; Sayangnya, POSIX takut akan karakter NUL dalam file teks dan belum memiliki terapi yang cukup :-) Saran saya sebenarnya menggunakan opsi non-portabel.
Jens
6
Dan Mac OS X (turunan BSD) telah finddengan -print0dan xargsdengan -0. Namun, AFAIK, HP-UX, AIX, dan Solaris tidak (tapi saya harus diperbaiki: HP-UX 11i tidak; Solaris 10 tidak; AIX 5.x tidak; tetapi mereka bukan versi saat ini ). Tidak akan sulit untuk mengubah sed, misalnya, untuk menggunakan 'baris' yang berakhir dengan '\0'alih - alih '\n', dan POSIX 2008 getdelim()akan membuatnya mudah untuk dikelola.
Jonathan Leffler
2
Trik +1 +1 untuk menggunakan jalur file yang berisi file daftar: cat $ file_paths_list_file | perl -ne | \ n | \ 000 | g; cetak '| xargs -0 zip $ zip_package
Yordan Georgiev
2
Ide bagus untuk mengganti baris baru dengan NUL - Saya harus melakukan ini pada sistem embedded yang tidak memiliki GNU menemukan atau GNU xargs atau perl - tetapi perintah tr dapat dimanfaatkan untuk melakukan hal yang sama: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms
joensson
28

Mencoba

find . -name \*.mp3 -print0 | xargs -0 mplayer

dari pada

ls | grep mp3 
Scott C Wilson
sumber
16

xargs pada MacOS tidak memiliki opsi -d, jadi solusi ini menggunakan -0 sebagai gantinya.

Dapatkan ls untuk mengeluarkan satu file per baris, lalu terjemahkan baris baru ke dalam nol dan beri tahu xargs untuk menggunakan nol sebagai pembatas:

ls -1 *mp3 | tr "\n" "\0" | xargs -0 mplayer

Graeme Pyle
sumber
8
find . -name 'Lemon*.mp3' -print0 | xargs 0 -i mplayer '{}' 

Ini membantu dalam kasus saya untuk menghapus file yang berbeda dengan spasi. Itu harus bekerja juga dengan mplayer. Trik yang diperlukan adalah tanda kutip. (Diuji pada Linux Xubuntu 14.04.)

Bendel
sumber
7

Jawaban Dick.Guertin [1] menyatakan bahwa seseorang dapat melarikan diri dari spasi dalam nama file adalah alternatif berharga untuk solusi lain yang disarankan di sini (seperti menggunakan karakter nol sebagai pemisah daripada spasi putih). Tapi itu bisa lebih sederhana - Anda tidak benar-benar membutuhkan karakter yang unik. Anda bisa menambahkan sedan ruang yang hilang secara langsung:

ls | grep ' ' | sed 's| |\\ |g' | xargs ...

Selain itu, grep hanya diperlukan jika Anda hanya ingin file dengan spasi di namanya. Lebih umum (misalnya, ketika memproses batch file beberapa di antaranya memiliki spasi, beberapa tidak), lewati saja grep:

ls | sed 's| |\\ |g' | xargs ...

Kemudian, tentu saja, nama file mungkin memiliki spasi putih selain kosong (misalnya, tab):

ls | sed -r 's|[[:blank:]]|\\\1|g' | xargs ...

Itu mengasumsikan Anda memiliki sed yang mendukung -r (extended regex) seperti GNU sed atau versi terbaru dari bsd sed (misalnya, FreeBSD yang awalnya mengeja opsi "-E" sebelum FreeBSD 8 dan mendukung -r & -E untuk kompatibilitas setidaknya melalui FreeBSD 11). Jika tidak, Anda dapat menggunakan ekspresi braket kelas regex karakter dasar dan secara manual memasukkan spasi dan karakter tab di []pembatas.

[1] Ini mungkin lebih tepat sebagai komentar atau edit untuk jawaban itu, tetapi saat ini saya tidak memiliki reputasi yang cukup untuk berkomentar dan hanya dapat menyarankan suntingan. Karena bentuk terakhir di atas (tanpa grep) mengubah perilaku jawaban asli Dick. Guertin, pengeditan langsung mungkin tidak tepat.

Juan
sumber
1
orang gila unix yang menjalankan skrip yang menamai file tanpa mempertimbangkan output mereka, itu yang
andrew lorien
4

ls | grep mp3 | sed -n "7p" | xargs -i mplayer {}

Perhatikan bahwa dalam perintah di atas, xargsakan memanggil mplayerlagi untuk setiap file. Ini mungkin tidak diinginkan untuk mplayer, tetapi mungkin oke untuk target lain.

Acumenus
sumber
1
Tambahan yang berguna untuk jawaban yang ada, tetapi perlu dicatat bahwa ini akan menyebabkan mplayerdisebut baru untuk setiap file. Itu penting jika Anda mencoba misalnya ... | xargs -I{} mplayer -shuffle {}: ini akan bermain dalam urutan yang sepenuhnya deterministik -shuffle.
1
Mungkin itu biasanya bukan maksudnya. xargssebagian besar digunakan dengan perintah yang menerima daftar nama file (contoh mudah:) rm, dan mencoba untuk mengirimkan nama file sebanyak yang dapat masuk ke setiap doa, hanya membelah menjadi beberapa doa jika diperlukan. Anda dapat melihat perbedaan ketika Anda menggunakan perintah di mana setiap doa terlihat, seperti echo(default): seq 0 100000 | xargsmencetak semua angka dari 0 hingga 23695 (khusus platform, tetapi itulah yang terjadi pada sistem saya) pada baris pertama, ke 45539 pada saluran 2, dll. Dan Anda benar, untuk sebagian besar perintah, itu tidak masalah.
4

Pada macOS 10.12.x (Sierra), jika Anda memiliki spasi dalam nama file atau subdirektori, Anda dapat menggunakan yang berikut ini:

find . -name '*.swift' -exec echo '"{}"' \; |xargs wc -l
Byron Formwalt
sumber
2

Itu tergantung pada (a) seberapa terlampir Anda ke nomor 7 yang bertentangan dengan, katakanlah, Lemon, dan (b) apakah ada nama file Anda yang berisi baris baru (dan apakah Anda bersedia untuk mengganti nama mereka jika mereka melakukannya).

Ada banyak cara untuk menghadapinya, tetapi beberapa di antaranya adalah:

mplayer Lemon*.mp3

find . -name 'Lemon*.mp3' -exec mplayer {} ';'

i=0
for mp3 in *.mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

for mp3 in *.mp3
do
    case "$mp3" in
    (Lemon*) mplayer "$mp3";;
    esac
done

i=0
find . -name *.mp3 |
while read mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

The readloop tidak bekerja jika nama file mengandung baris; yang lain bekerja dengan benar bahkan dengan baris baru di namanya (apalagi spasi). Untuk uang saya, jika Anda memiliki nama file yang mengandung baris baru, Anda harus mengganti nama file tanpa baris baru. Menggunakan tanda kutip ganda di sekitar nama file adalah kunci untuk loop bekerja dengan benar.

Jika Anda memiliki GNU finddan GNU xargs(atau FreeBSD (* BSD?), Atau Mac OS X), Anda juga dapat menggunakan opsi -print0dan -0, seperti pada:

find . -name 'Lemon*.mp3' -print0 | xargs -0 mplayer

Ini berfungsi terlepas dari isi nama (hanya dua karakter yang tidak dapat muncul dalam nama file adalah slash dan NUL, dan slash tidak menyebabkan masalah dalam jalur file, jadi menggunakan NUL sebagai pembatas nama mencakup semuanya). Namun, jika Anda perlu menyaring 6 entri pertama, Anda memerlukan program yang menangani 'baris' yang diakhiri dengan NUL alih-alih baris baru ... dan saya tidak yakin ada.

Yang pertama adalah yang paling sederhana untuk kasus spesifik yang ada; namun, itu mungkin tidak menggeneralisasi untuk menutupi skenario Anda yang belum Anda daftarkan.

Jonathan Leffler
sumber
2

Aku tahu bahwa aku tidak menjawab xargspertanyaan secara langsung , tapi itu layak disebut find's -execpilihan.

Diberikan sistem file berikut:

[root@localhost bokeh]# tree --charset assci bands
bands
|-- Dream\ Theater
|-- King's\ X
|-- Megadeth
`-- Rush

0 directories, 4 files

Perintah find dapat dibuat untuk menangani ruang di Dream Theater dan King's X. Jadi, untuk menemukan drummer dari setiap band menggunakan grep:

[root@localhost]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

Dalam -execopsi {}singkatan nama file termasuk jalur. Perhatikan bahwa Anda tidak perlu menghindarinya atau menuliskannya dalam tanda kutip.

Perbedaan antara -execterminator ( +dan \;) adalah +mengelompokkan sebanyak mungkin nama file ke satu baris perintah. Sedangkan \;akan menjalankan perintah untuk setiap nama file.

Jadi, find bands/ -type f -exec grep Drums {} +akan menghasilkan:

grep Drums "bands/Dream Theater" "bands/Rush" "bands/King's X" "bands/Megadeth"

dan find bands/ -type f -exec grep Drums {} \;akan menghasilkan:

grep Drums "bands/Dream Theater"
grep Drums "bands/Rush"
grep Drums "bands/King's X"
grep Drums "bands/Megadeth"

Dalam hal grepini memiliki efek samping baik mencetak nama file atau tidak.

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} \;
Drums:Mike Mangini
Drums: Neil Peart
Drums:Jerry Gaskill
Drums:Dirk Verbeuren

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

Tentu saja, grepopsi -hdan -Hakan mengontrol apakah nama file dicetak terlepas dari bagaimana grepdipanggil.


xargs

xargs juga dapat mengontrol bagaimana file manusia berada di baris perintah.

xargssecara default mengelompokkan semua argumen ke dalam satu baris. Untuk melakukan hal yang sama yang -exec \;digunakan xargs -l. Perhatikan bahwa -topsi memberi tahu xargsuntuk mencetak perintah sebelum menjalankannya.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n' -l -t grep Drums
grep Drums ./bands/Dream Theater 
Drums:Mike Mangini
grep Drums ./bands/Rush 
Drums: Neil Peart
grep Drums ./bands/King's X 
Drums:Jerry Gaskill
grep Drums ./bands/Megadeth 
Drums:Dirk Verbeuren

Lihat bahwa -lopsi memberi tahu xargs untuk menjalankan grep untuk setiap nama file.

Lawan default (yaitu tidak ada -lopsi):

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush ./bands/King's X ./bands/Megadeth 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren

xargsmemiliki kontrol yang lebih baik pada berapa banyak file yang dapat berada di baris perintah. Berikan -lopsi jumlah maksimal file per perintah.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -l2 -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
grep Drums ./bands/King's X ./bands/Megadeth 
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren
[root@localhost bokeh]# 

Lihat yang grepdieksekusi dengan dua nama file karena -l2.

shrewmouse
sumber
1

Diberikan judul spesifik dari posting ini, inilah saran saya:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g'

Idenya adalah untuk mengonversi blank menjadi karakter unik apa pun, seperti '<', dan kemudian mengubahnya menjadi '\', garis miring terbalik diikuti oleh tanda kosong. Anda kemudian dapat mengirimkannya ke perintah apa pun yang Anda suka, seperti:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g' | xargs -L1 GetFileInfo

Kuncinya di sini terletak pada perintah 'tr' dan 'sed'; dan Anda dapat menggunakan karakter apa pun selain '<', seperti '?' atau bahkan karakter tab.

Dick. Guertin
sumber
Apa tujuan dari jalan memutar melalui tr? Kenapa tidak adil ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
tripleee
1
Saya telah menemukan bahwa "tr '' '?'" Menghilangkan kebutuhan akan "sed". Tunggal "?" karakter tidak kosong, tetapi cocok dengan karakter tunggal APAPUN, dalam hal ini: kosong. Kemungkinannya menjadi sesuatu yang lain cukup kecil, dan dapat diterima karena Anda mencoba memproses SEMUA file yang berakhiran .mp3: "ls | grep '' | tr '' '?' | xargs -L1 GetFileInfo "
Dick Guertin
Anda juga dapat menangani "tab" secara bersamaan: tr '\ t' '??' menangani keduanya.
Dick Guertin
1

Solusi alternatif dapat membantu ...

Anda juga dapat menambahkan karakter nol ke akhir baris Anda menggunakan Perl, lalu gunakan -0opsi dalam xargs. Berbeda dengan xargs -d '\ n' (dalam jawaban yang disetujui) - ini bekerja di mana-mana, termasuk OS X.

Misalnya, untuk mendaftar secara rekursif (mengeksekusi, memindahkan, dll.) File MPEG3 yang mungkin berisi spasi atau karakter lucu lainnya - Saya akan menggunakan:

find . | grep \.mp3 | perl -ne 'chop; print "$_\0"' | xargs -0  ls

(Catatan: Untuk memfilter, saya lebih suka sintaks "| grep" yang lebih mudah diingat daripada argumen "find's" --name.)

Charlie Dalsass
sumber