temukan exec '{}' tidak tersedia setelah>

8

Exec memungkinkan kita untuk melewati semua argumen sekaligus dengan {} +atau meneruskannya satu per satu dengan{} \;

Sekarang katakanlah saya ingin mengganti nama semua jpeg , tidak masalah melakukan ini:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec mv '{}' '{}'.new \;

Tetapi jika saya perlu mengarahkan ulang output, '{}'tidak dapat diakses setelah pengalihan.

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjpeg -quality 80 '{}' > optimized_'{}' \;

Ini tidak akan berhasil. Saya harus menggunakan for for, menyimpan output find ke dalam variabel sebelum menggunakannya. Mari kita akui, itu merepotkan.

for f in `find . \( -name '*.jpg' -o -name '*.jpeg' \)`; do cjpeg -quality 80 $f > optimized_$f; done;

Jadi adakah cara yang lebih baik?

Buzut
sumber
Apakah tidak ada yang >hilang dalam sampel kode ketiga?
choroba
1
Bahkan baris pertama Anda tidak standar dan dengan demikian tidak portabel. Cobalah untuk menghindari baris perintah yang {}muncul dalam string yang lebih panjang karena string seperti itu biasanya tidak diperluas.
schily
Contoh pertama itu tidak melakukan apa yang Anda katakan.
ctrl-alt-delor
1
Anda telah memperbaiki pertanyaan Anda: Saya tidak akan lagi mengubahnya.
ctrl-alt-delor
1
Untuk apa nilainya, alasan utama bahwa pengalihan Anda tidak berfungsi seperti yang Anda inginkan adalah bahwa itu ditangani oleh shell dari mana Anda memulai find, satu kali, dan diterapkan pada findperintah itu sendiri. Tidak {}memiliki makna khusus dalam konteks itu. Pengalihan bukan argumen untuk find, dan tentu saja bukan bagian dari -execklausa.
John Bollinger

Jawaban:

13

Anda bisa menggunakan bash -cdalam find -execperintah dan menggunakan parameter posisi dengan perintah bash:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec bash -c 'cjpeg -quality 80 "$1" > "$(dirname "$1")/optimized_$(basename "$1")"' sh {} \;

Dengan cara {}itu disediakan $1.

The shsebelum {}menceritakan shell batin "nama" nya, string yang digunakan di sini digunakan dalam misalnya pesan kesalahan. Ini dibahas lebih lanjut dalam jawaban ini di stackoverflow .

oliv
sumber
8

Anda punya jawaban ( https://unix.stackexchange.com/a/481687/4778 ), tetapi inilah sebabnya.

Pengalihan >, dan juga pipa |, dan $ekspansi, semua dilakukan oleh shell sebelum perintah dieksekusi. Oleh karena itu stdout dialihkan ke optimized_{}, sebelum finddimulai.

ctrl-alt-delor
sumber
3

Pengalihan perlu dikutip untuk menghindari bahwa shell yang sekarang menafsirkannya.
Tetapi mengutipnya juga akan menghindari output dari perintah untuk diarahkan.
Solusi yang dikenal untuk ini adalah dengan memanggil shell:

find . -name '*.jpg' -exec sh -c 'echo "$1" >"$1".new' called_shell '{}' \;

Dalam hal ini, pengalihan ( >) dikutip pada shell sekarang dan bekerja dengan benar di dalam shell yang disebut. Ini called_shelldigunakan sebagai $0parameter (nama) dari shell anak ( sh).

Itu bekerja dengan baik jika sufiks ditambahkan nama file, tetapi tidak jika Anda menggunakan awalan. Agar awalan berfungsi, Anda harus menghapus ./yang ditemukan sebelumnya dengan nama file ${1#./}dan menggunakan -execdiropsi.

Anda mungkin (atau mungkin tidak) ingin menggunakan -inameopsi sehingga file yang bernama *.JPGatau *.JpGvariasi lain juga disertakan.

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     cjpeg -quality 80 "$1" > optimized_"${1#./}"
     ' called_shell '{}' \;

Dan, Anda mungkin (atau mungkin tidak) juga ingin memanggil shell sekali per direktori alih-alih sekali per file dengan menambahkan loop ( for f do … ; done) dan a +di akhir:

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     for f; do cjpeg -quality 80 "$f" > optimized_"${f#./}"; done
     ' called_shell '{}' \+

Dan, akhirnya, karena cjpegdapat langsung menulis ke file, pengalihan dapat dihindari sebagai:

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     for f; do cjpeg -quality 80 "$f" -outfile optimized_"${f#./}"; done
     ' called_shell '{}' \+
Ishak
sumber
3

cjpegmemiliki opsi yang memungkinkan Anda menulis ke file bernama, daripada output standar. Jika versi Anda findmendukung -execdiropsi, Anda dapat memanfaatkannya untuk membuat pengalihan tidak perlu.

find . \( -name '*.jpg' -o -name '*.jpeg' \) \
  -execdir cjpeg -quality 80 -outfile optimized_'{}' '{}' \;

Catatan: ini benar-benar mengasumsikan versi BSD find, yang muncul untuk menghapus yang terdepan ./dari nama file saat exanding {}. (Atau sebaliknya, GNU find menambahkan ./ namanya. Tidak ada standar untuk mengatakan perilaku mana yang "benar".)

chepner
sumber
Jika Anda findmendukung -execdir, Anda bisa menggunakannya -exec. Ini menyebabkan perintah dijalankan di direktori tempat file ditemukan, dan {}akan menjadi aa.jpgbukan ./t2/aa.jpg.
chepner
Masih dalam kesalahan: cjpeg: can't open optimized_./aa.jpg.
Isaac
Hm, itu tampaknya merupakan perbedaan antara GNU finddan BSD find. (Bahaya menggunakan ekstensi non-standar.)
chepner
Nama file tanpa pemimpin ./tampaknya lebih rentan terhadap kesalahan. Solusi yang masuk akal diusulkan dalam jawaban ini .
Isaac
1

Buat skrip cjq80:

#!/bin/bash
cjpeg -quality 80 "$1" > "${1%/*}"/optimized_"${1##*/}"

Jadikan itu dapat dieksekusi

chmod u+x cjq80

Dan gunakan di -exec:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjq80 '{}' \;
choroba
sumber
Saya punya tentang hal ini, tetapi itu tidak terlalu berguna. Terutama karena saya harus memasukkan ini dalam proses pembangunan
Buzut
Cukup tambahkan skrip ke skrip build lain, menurut saya skrip ini lebih mudah dibaca daripada bash -c bersarang ganda.
choroba
Ya, ini masalah selera. Sekarang setiap opsi ditentukan sehingga jika seseorang menemukan masalah yang sama, ia akan memilih untuk dirinya sendiri :)
Buzut