Bagaimana cara meneruskan wildcard '*' ke path parameter perintah find melalui variabel dalam skrip?

9

Saya ingin menggunakan finduntuk menemukan file dalam satu set folder yang dibatasi oleh wildcard, tetapi di mana ada spasi dalam nama path.

Dari baris perintah, ini mudah. Contoh-contoh berikut semuanya berfungsi.

find  te*/my\ files/more   -print
find  te*/'my files'/more  -print
find  te*/my' 'files/more  -print

Ini akan menemukan file di, misalnya, terminal/my files/moredan tepid/my files/more.

Namun, saya ingin ini menjadi bagian dari naskah; yang saya butuhkan adalah sesuatu seperti ini:

SEARCH='te*/my\ files/more'
find ${SEARCH} -print

Sayangnya, apa pun yang saya lakukan, saya tampaknya tidak dapat mencampur wildcard dan spasi dalam findperintah dalam skrip. Contoh di atas mengembalikan kesalahan berikut (perhatikan penggandaan backslash yang tidak terduga):

find: te*/my\\’: No such file or directory
find: files/more’: No such file or directory

Mencoba menggunakan tanda kutip juga gagal.

SEARCH="te*/'my files'/more"
find ${SEARCH} -print

Ini mengembalikan kesalahan berikut, setelah mengabaikan arti dari kutipan:

find: te*/'my’: No such file or directory
find: ‘files'/more’: No such file or directory

Ini satu contoh lagi.

SEARCH='te*/my files/more'
find ${SEARCH} -print

Seperti yang diharapkan:

find: te*/my’: No such file or directory
find: files/more’: No such file or directory

Setiap variasi yang saya coba mengembalikan kesalahan.

Saya memiliki solusi, yang berpotensi berbahaya karena mengembalikan terlalu banyak folder. Saya mengonversi semua spasi menjadi tanda tanya (wildcard satu karakter) seperti ini:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /?}       # Convert every space to a question mark.
find ${SEARCH} -print

Ini setara dengan:

find te*/my?files/more -print

Ini mengembalikan tidak hanya folder yang benar tetapi juga terse/myxfiles/more, yang seharusnya tidak.

Bagaimana saya bisa mencapai apa yang saya coba lakukan? Google belum membantu saya :(

Paddy Landau
sumber
@KasiyA Saya menggunakan bash; Anda harus menggunakan sesuatu yang lain, karena saya belum pernah melihat konstruksi itu sebelumnya. Perintah menghasilkan SEARCH: command not foundperintah find -printyang dieksekusi.
Paddy Landau
Tembakan dalam gelap, tapi bagaimana dengan mengutip? find "${SEARCH}" -print?
Alaa Ali
@AlaaAli Tidak, tidak berfungsi, karena mengutip mencegah Bash menggunakan wildcard. Ini akan mencari jalur khusus dengan nama (dalam contoh saya) te*/'my files'/more.
Paddy Landau

Jawaban:

9

Perintah yang sama persis harus bekerja dengan baik dalam sebuah skrip:

#!/usr/bin/env bash
find  te*/my\ files/ -print

Jika Anda perlu memilikinya sebagai variabel, itu akan menjadi sedikit lebih kompleks:

#!/usr/bin/env bash
search='te*/my\ files/'
eval find "$search" -print

PERINGATAN:

Menggunakan evalseperti itu tidak aman dan dapat mengakibatkan mengeksekusi kode yang sewenang-wenang dan mungkin berbahaya jika nama file Anda dapat berisi karakter tertentu. Lihat bash FAQ 48 untuk detailnya.

Lebih baik melewati jalan sebagai argumen:

#!/usr/bin/env bash
find "$@" -name "file*"

Pendekatan lain adalah untuk menghindari findsama sekali dan menggunakan fitur globing dan bash global yang diperluas:

#!/usr/bin/env bash
shopt -s globstar
for file in te*/my\ files/**; do echo "$file"; done

The globstarpilihan pesta memungkinkan Anda menggunakan **untuk mencocokkan rekursif:

globstar
      If set, the pattern ** used in a pathname expansion con
      text will match all files and zero or  more  directories
      and  subdirectories.  If the pattern is followed by a /,
      only directories and subdirectories match.

Untuk membuatnya bertindak 100% suka menemukan dan memasukkan file dot (file tersembunyi), gunakan

#!/usr/bin/env bash
shopt -s globstar
shopt -s dotglob
for file in te*/my\ files/**; do echo "$file"; done

Anda dapat meratakannya echosecara langsung tanpa loop:

echo te*/my\ files/**
terdon
sumber
2
Saya telah menetapkan ini sebagai jawaban karena komentar bermanfaat yang telah terdon buat (tidak melupakan komentar bermanfaat dari orang lain). Saya telah menggunakan kemampuan globing Bash di baris perintah untuk meneruskan beberapa jalur ke skrip saya, alih-alih skrip yang mencoba mengatasinya. Ini bekerja dengan baik.
Paddy Landau
2

Bagaimana dengan array?

$ tree Desktop/ Documents/
Desktop/
└── my folder
    └── more
        └── file
Documents/
└── my folder
    ├── folder
    └── more

5 directories, 1 file
$ SEARCH=(D*/my\ folder)
$ find "${SEARCH[@]}" 
Desktop/my folder
Desktop/my folder/more
Desktop/my folder/more/file
Documents/my folder
Documents/my folder/more
Documents/my folder/folder

(*)memperluas ke array apa pun yang cocok dengan wildcard. Dan "${SEARCH[@]}"mengembang ke semua elemen dalam array ( [@]), dengan masing-masing dikutip secara individual.

Terlambat, saya menyadari bahwa dirinya harus mampu melakukan ini. Sesuatu seperti:

find . -path 'D*/my folder/more/'
muru
sumber
Ide yang cerdas, tetapi sayangnya, itu tidak berhasil. Mengapa? Karena jalur itu sendiri disimpan dalam variabel; karenanya, INPUTPATH='te*/my files/moredan SEARCH=(${INPUTPATH}). Tidak peduli bagaimana saya memvariasikan cara saya melakukan ini, saya tetap berakhir dengan hasil yang tidak fungsional. Ini sepertinya tidak mungkin!
Paddy Landau
Ini semua benar tentu saja, tetapi OP perlu melakukannya dalam sebuah skrip. Itu mengubah banyak hal sejak ekspansi wildcard menjadi jauh lebih kompleks dan ini tidak berhasil.
terdon
@PaddyLandau Dalam hal ini, mengapa bisa Anda tidak menggunakan find's -pathfilter? Ini menggunakan wildcard dan jelas tidak perlu ekspansi.
muru
@uru Itu menarik; Saya tidak tahu -path. Namun, dalam 10 menit terakhir, saya telah menemukan jawabannya: gunakan eval! Tampaknya lebih sederhana daripada -path.
Paddy Landau
2
@terdon dan muru dan semuanya: Terima kasih. Saya telah mendengar apa yang Anda semua katakan, dan saya menyadari bahwa saya harus membuat skrip saya melakukan satu hal, dan membiarkan Bash globbing melewati banyak file atau jalur ke skrip. Saya telah memodifikasi skrip saya. Ini bekerja dengan baik dan lebih cocok dengan filosofi Linux. Terima kasih lagi!
Paddy Landau
0

Saya akhirnya menemukan jawabannya.

Tambahkan garis miring terbalik ke semua spasi:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /\\ }

Pada titik ini, SEARCHberisi te*/my\ files/more.

Lalu, gunakan eval.

eval find ${SEARCH} -print

Sesederhana itu! Menggunakan evalmemotong interpretasi yang ${SEARCH}berasal dari variabel.

Paddy Landau
sumber
Tolong jangan. .
terdon
@terdon Terima kasih atas peringatannya. Kembali ke papan gambar!
Paddy Landau
Ya, ini sangat sulit. Saya baru saja memperbarui jawaban saya dengan pendekatan lain, mengapa tidak menggunakan globbing saja? Jika itu masih tidak berhasil, saya sarankan Anda memposting pertanyaan baru di Unix & Linux menjelaskan apa tujuan akhir Anda dan mengapa Anda perlu memiliki pola sebagai variabel. Jenis hal ini lebih disukai untuk mendapatkan jawaban yang lebih baik di sana.
terdon