Memanipulasi nama file yang disalurkan dari perintah find

10

Saya relatif baru di Bash dan saya sedang mencoba melakukan sesuatu yang pada permukaannya tampak cukup mudah - jalankan find melalui hierarki direktori untuk mendapatkan semua file * .wma, pipa yang di-output ke perintah di mana saya mengkonversikannya ke mp3 dan simpan file yang dikonversi sebagai .mp3. Pemikiran saya adalah bahwa perintah tersebut harus terlihat seperti berikut (Saya telah meninggalkan perintah konversi audio dan saya menggunakan gema untuk ilustrasi):

$ find ./ -name '*.wma' -type f -print0 | xargs -0 -I f echo ${f%.*}.mp3

Seperti yang saya pahami, argumen -print0 akan membiarkan saya menangani nama file yang memiliki spasi (yang banyak di antaranya dilakukan sebagai file musik). Saya kemudian mengharapkan (sebagai hasil dari xargs) bahwa setiap path file dari find ditangkap dalam f, dan bahwa menggunakan pencocokan substring / delete dari akhir string, bahwa saya harus menggemakan path file asli dengan mp3 ekstensi bukan wma. Namun, alih-alih hasil ini, saya melihat yang berikut:

*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
...

Jadi pertanyaan saya (selain dari spesifik 'apa yang saya lakukan salah di sini'), apakah ini - apakah nilai - nilai yang merupakan hasil dari operasi pipa perlu diperlakukan secara berbeda dalam operasi manipulasi string daripada yang merupakan hasil dari penugasan variabel ?

Howard Dierking
sumber
1
Tidak ada alasan untuk menggunakan xargsdengan find. Muncul dengan -execopsi. Bisakah Anda menambahkan perintah yang akan Anda gunakan untuk pertanyaan Anda, dan seseorang dapat menunjukkan kepada Anda findperintah yang benar ?
ixtmixilix
ya (seperti yang saya catat di bawah), selama saya masih bisa melakukan manipulasi string pada setiap hasil dari perintah find (misalnya {}anggota)
Howard Dierking
sebenarnya ada kasus tepi di mana xargslebih cocok daripada exec. Lihat tumpukan-tumpukan ini stackoverflow.com/questions/896808/find-exec-cmd-vs-xargs untuk sebuah case in point.
d34dh0r53
@ d34dh0r53 Kasing tepi apa? Utas yang Anda tautkan tidak menunjukkan apa pun.
Gilles 'SANGAT berhenti menjadi jahat'

Jawaban:

8

Seperti jawaban lain telah diidentifikasi, ${f%.*}diperluas oleh shell sebelum menjalankan xargsperintah. Anda perlu ekspansi ini terjadi sekali untuk setiap nama file, dengan variabel shell fdiatur ke nama file (lewat -I ftidak melakukan itu: xargstidak memiliki gagasan variabel shell, itu mencari string fdalam perintah, jadi jika Anda ingin digunakan misalnya xargs -I e echo …akan mengeksekusi perintah seperti ./somedir/somefile.wmacho .mp3).

Terus menggunakan pendekatan ini, beri tahu xargsuntuk memanggil shell yang dapat melakukan ekspansi. Lebih baik, katakan find- xargsadalah alat yang sebagian besar sudah usang dan sulit digunakan dengan benar, karena versi modern find memiliki konstruk yang melakukan pekerjaan yang sama (dan lebih banyak lagi) dengan lebih sedikit kesulitan pipa. Alih-alih find … -print0 | xargs -0 command …, jalankan find … -exec command … {} +.

find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +

Argumennya _ada $0di shell; nama file dilewatkan sebagai argumen posisional yang for f; do …berulang. Versi sederhana dari perintah ini mengeksekusi shell terpisah untuk setiap file, yang setara tetapi sedikit lebih lambat:

find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;

Anda sebenarnya tidak perlu menggunakan di findsini, dengan asumsi Anda menjalankan shell yang cukup baru (ksh93, bash ≥4.0, atau zsh). Di bash, masukkan shopt -s globstarAnda .bashrcuntuk mengaktifkan **/pola glob untuk muncul kembali di subdirektori (dalam ksh, itu set -o globstar). Maka Anda bisa lari

for f in **/*.wma; do
  echo "${f%.*}.mp3"
done

(Jika Anda memiliki direktori yang dipanggil *.wma, tambahkan [ -f "$f" ] || continuedi awal loop.)

Gilles 'SANGAT berhenti menjadi jahat'
sumber
penjelasan hebat serta mengarahkan saya ke arah yang bahkan tidak saya sadari (memutakhirkan bash shell saya untuk mendapatkan fungsi globstar) - pada akhirnya, globbing rekursif adalah solusi yang menjaga perintah tetap sederhana dan menyelesaikan semua yang saya coba lakukan . Terima kasih!
Howard Dierking
6

Dalam hal evaluasi solusi Anda sebesar $ {f%. *}. Mp3 dilakukan di shell yang Anda gunakan untuk menjalankan seluruh perintah, bukan di shell yang dikerjakan oleh xargs . Dan di shell Anda tidak ada variabel f , itu diganti dengan string kosong.

Solusi menggunakan xargs dengan -I f :

% cat 1.sh 
#!/bin/sh
f=$1
echo ${f%.*}.mp3
% /bin/ls -1
1.sh
aaa.wma
bbbb.wma
ccccc.wma
% find ./ -name '*.wma' -type f -print0 | xargs -0 -I f ./1.sh f
./aaa.mp3
./ccccc.mp3
./bbbb.mp3

Tetapi saya akan melakukannya dengan cara ini:

% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'
0xFF
sumber