Bagaimana saya bisa menggunakan wildcard terbalik atau negatif ketika pencocokan pola di shell unix / linux?

325

Katakanlah saya ingin menyalin isi direktori tidak termasuk file dan folder yang namanya mengandung kata 'Musik'.

cp [exclude-matches] *Music* /target_directory

Apa yang harus dilakukan sebagai pengganti [kecocokan kecocokan] untuk mencapai ini?

pengguna4812
sumber

Jawaban:

375

Di Bash Anda dapat melakukannya dengan mengaktifkan extglobopsi, seperti ini (ganti lsdengan cpdan tambahkan direktori target, tentu saja)

~/foobar> shopt extglob
extglob        off
~/foobar> ls
abar  afoo  bbar  bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob  # Enables extglob
~/foobar> ls !(b*)
abar  afoo
~/foobar> ls !(a*)
bbar  bfoo
~/foobar> ls !(*foo)
abar  bbar

Anda nanti dapat menonaktifkan extglob dengan

shopt -u extglob
Vinko Vrsalovic
sumber
14
Saya suka fitur ini:ls /dir/*/!(base*)
Erick Robertson
6
Bagaimana Anda memasukkan semuanya ( ) dan juga mengecualikan! (B )?
Elijah Lynn
4
Bagaimana Anda mencocokkan, katakanlah, semuanya dimulai dengan f, kecuali foo?
Noldorin
8
Mengapa ini dinonaktifkan secara default?
weberc2
3
shopt -o -u histexpand jika Anda perlu mencari file dengan tanda seru di dalamnya - diaktifkan secara default, extglob dimatikan secara default sehingga tidak mengganggu histexpand, dalam dokumen itu dijelaskan mengapa hal ini terjadi. cocok dengan semua yang dimulai dengan f kecuali foo: f! (oo), tentu saja 'makanan' masih akan cocok (Anda perlu f! (oo *) untuk menghentikan hal-hal yang dimulai dengan 'foo' atau, jika Anda ingin menyingkirkan hal-hal tertentu yang diakhiri dengan penggunaan '.foo'! ( .foo), atau diawali: myprefix! ( .foo) (cocok dengan myprefixBLAH tetapi tidak myprefixBLAH.foo)
osirisgothra
227

The extglobpilihan shell memberi Anda pencocokan pola yang lebih kuat pada command line.

Anda menyalakannya shopt -s extglob, dan mematikannya dengan shopt -u extglob.

Dalam contoh Anda, pada awalnya Anda akan melakukan:

$ shopt -s extglob
$ cp !(*Music*) /target_directory

Tersedia penuh ext berakhir gumpal operator bing adalah (kutipan dari man bash):

Jika opsi extglob shell diaktifkan menggunakan shopt builtin, beberapa operator pencocokan pola yang diperluas dikenali. Daftar pola adalah daftar satu atau lebih pola yang dipisahkan oleh |. Pola komposit dapat dibentuk menggunakan satu atau lebih dari sub-pola berikut:

  • ? (daftar pola)
    Cocok dengan nol atau satu kemunculan pola yang diberikan
  • * (daftar pola)
    Cocok dengan nol atau lebih kemunculan pola yang diberikan
  • + (daftar pola)
    Cocok dengan satu atau lebih kemunculan pola yang diberikan
  • @ (daftar pola)
    Cocok dengan salah satu pola yang diberikan
  • ! (daftar pola)
    Cocokkan apa pun kecuali salah satu pola yang diberikan

Jadi, misalnya, jika Anda ingin membuat daftar semua file di direktori saat ini yang bukan .catau .hfile, Anda akan melakukan:

$ ls -d !(*@(.c|.h))

Tentu saja, shell global globing berfungsi dengan baik, jadi contoh terakhir juga dapat ditulis sebagai:

$ ls -d !(*.[ch])
tzot
sumber
1
Apa alasan -d?
Big McLargeHuge
2
@Koveras untuk kasus salah satu .catau .hfile tersebut adalah direktori.
tzot
@DaveKennedy Ini untuk mendaftar semua yang ada di direktori saat ini D, tetapi bukan isi dari subdirektori yang mungkin terkandung dalam direktori D.
spurra
23

Bukan di bash (yang saya tahu), tetapi:

cp `ls | grep -v Music` /target_directory

Saya tahu ini bukan apa yang Anda cari, tetapi ini akan memecahkan contoh Anda.

ejgottl
sumber
Default ls akan meletakkan banyak file per baris, yang mungkin tidak akan memberikan hasil yang benar.
Daniel Bungert
10
Hanya ketika stdout adalah terminal. Saat digunakan dalam pipa, ls mencetak satu nama file per baris.
Adam Rosenfield
ls hanya menempatkan banyak file per baris jika keluaran ke terminal. Coba sendiri - "ls | less" tidak akan pernah memiliki banyak file per baris.
SpoonMeiser
3
Itu tidak akan berfungsi untuk nama file yang mengandung spasi (atau karakter spasi putih lainnya).
tzot
7

Jika Anda ingin menghindari biaya mem menggunakan perintah exec, saya yakin Anda bisa melakukan yang lebih baik dengan xargs. Saya pikir berikut ini adalah alternatif yang lebih efisien

find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec



find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
Steve
sumber
6

Dalam bash, alternatif shopt -s extglobadalah GLOBIGNOREvariabel . Ini tidak benar-benar lebih baik, tetapi saya merasa lebih mudah diingat.

Contoh yang mungkin diinginkan oleh pengirim aslinya:

GLOBIGNORE="*techno*"; cp *Music* /only_good_music/

Setelah selesai, unset GLOBIGNOREuntuk dapat rm *techno*di direktori sumber.

mivk
sumber
5

Anda juga dapat menggunakan forloop yang cukup sederhana :

for f in `find . -not -name "*Music*"`
do
    cp $f /target/dir
done
mipadi
sumber
1
Ini merupakan penemuan rekursif, yang merupakan perilaku berbeda dari yang diinginkan OP.
Adam Rosenfield
1
gunakan -maxdepth 1untuk non-rekursif?
avtomaton
Saya menemukan ini menjadi solusi terbersih tanpa harus mengaktifkan / menonaktifkan opsi shell. Opsi -maxdepth akan direkomendasikan dalam posting ini untuk mendapatkan hasil yang dibutuhkan oleh OP, tetapi semuanya tergantung pada apa yang ingin Anda capai.
David Lapointe
Penggunaan finddi backticks akan pecah dengan cara yang tidak menyenangkan jika ia menemukan nama file tidak trivial.
tripleee
5

Preferensi pribadi saya adalah menggunakan grep dan perintah while. Ini memungkinkan seseorang untuk menulis skrip yang kuat namun dapat dibaca memastikan bahwa Anda akhirnya melakukan apa yang Anda inginkan. Ditambah dengan menggunakan perintah echo Anda dapat melakukan dry run sebelum melakukan operasi yang sebenarnya. Sebagai contoh:

ls | grep -v "Music" | while read filename
do
echo $filename
done

akan mencetak file yang akhirnya akan Anda salin. Jika daftar sudah benar, langkah selanjutnya adalah cukup mengganti perintah gema dengan perintah salin sebagai berikut:

ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
Abid H. Mujtaba
sumber
1
Ini akan berfungsi selama nama file Anda tidak memiliki tab, baris baru, lebih dari satu spasi dalam satu baris, atau garis miring terbalik apa pun. Sementara itu adalah kasus-kasus patologis, ada baiknya menyadari kemungkinan itu. Di bashAnda dapat menggunakan while IFS='' read -r filename, tapi kemudian baris baru masih menjadi masalah. Secara umum yang terbaik adalah tidak menggunakan lsuntuk menghitung file; alat seperti findlebih cocok.
Thedward
Tanpa alat tambahan:for file in *; do case ${file} in (*Music*) ;; (*) cp "${file}" /target_directory ; echo ;; esac; done
Thedward
mywiki.wooledge.org/ParsingLs mencantumkan sejumlah alasan tambahan mengapa Anda harus menghindari ini.
tripleee
5

Sebuah trik yang saya belum melihat di sini belum yang tidak menggunakan extglob, findatau grepuntuk mengobati dua daftar file sebagai set dan "diff" dengan menggunakan comm:

comm -23 <(ls) <(ls *Music*)

commlebih disukai daripada diffkarena tidak memiliki cruft tambahan.

Ini mengembalikan semua elemen dari set 1 ls,, yang tidak juga di set 2 ls *Music*,. Ini membutuhkan kedua set agar diurutkan agar berfungsi dengan benar. Tidak ada masalah untuk lsdan ekspansi global, tetapi jika Anda menggunakan sesuatu seperti find, pastikan untuk memohon sort.

comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)

Berpotensi berguna.

James M. Lay
sumber
1
Salah satu manfaat dari pengecualian ini adalah untuk tidak melintasi direktori sejak awal. Solusi ini melakukan dua traversal dari sub-direktori - satu dengan pengecualian dan satu tanpa.
Mark Stosberg
Poin yang sangat bagus, @MarkStosberg. Meskipun, satu manfaat tambahan dari teknik ini adalah Anda dapat membaca pengecualian dari file yang sebenarnya, misalnyacomm -23 <(ls) exclude_these.list
James M. Lay
3

Satu solusi untuk ini dapat ditemukan dengan find.

$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt

Temukan memiliki beberapa opsi, Anda bisa mendapatkan cukup spesifik tentang apa yang Anda sertakan dan kecualikan.

Sunting: Adam dalam komentar mencatat bahwa ini bersifat rekursif. menemukan opsi mindepth dan maxdepth dapat berguna dalam mengendalikan ini.

Daniel Bungert
sumber
Ini melakukan salinan rekursif, yang merupakan perilaku yang berbeda. Ini juga memunculkan proses baru untuk setiap file, yang bisa sangat tidak efisien untuk sejumlah besar file.
Adam Rosenfield
Biaya pemijahan suatu proses adalah sekitar nol dibandingkan dengan semua IO yang menyalin setiap file yang dihasilkan. Jadi saya akan mengatakan ini cukup baik untuk penggunaan sesekali.
dland
Beberapa solusi untuk proses pemijahan: stackoverflow.com/questions/186099/…
Vinko Vrsalovic
gunakan "-maxdepth 1" untuk menghindari rekursi.
ejgottl
gunakan backticks untuk mendapatkan analog dari ekspansi wild card shell: cp find -maxdepth 1 -not -name '*Music*'/ target_directory
ejgottl
2

Karya-karya berikut mencantumkan semua *.txtfile dalam direktori saat ini, kecuali yang dimulai dengan angka.

Ini bekerja di bash, dash, zshdan semua kerang kompatibel POSIX lainnya.

for FILE in /some/dir/*.txt; do    # for each *.txt file
    case "${FILE##*/}" in          #   if file basename...
        [0-9]*) continue ;;        #   starts with digit: skip
    esac
    ## otherwise, do stuff with $FILE here
done
  1. Sejalan satu pola /some/dir/*.txtakan menyebabkan forloop untuk mengulangi semua file /some/diryang namanya berakhir dengan .txt.

  2. Dalam baris kedua, pernyataan kasus digunakan untuk menyingkirkan file yang tidak diinginkan. - ${FILE##*/}Ekspresi menghapus komponen dir nama terkemuka dari nama file (di sini /some/dir/) sehingga patters dapat mencocokkan dengan hanya nama file. (Jika Anda hanya membuang nama file berdasarkan sufiks, Anda dapat mempersingkat ini menjadi $FILE.)

  3. Pada baris ketiga, semua file yang cocok dengan casepola [0-9]*) baris akan dilewati ( continuepernyataan melompat ke iterasi forloop berikutnya). - Jika Anda ingin, Anda dapat melakukan sesuatu yang lebih menarik di sini, misalnya seperti melewatkan semua file yang tidak dimulai dengan huruf (a – z) menggunakan [!a-z]*, atau Anda dapat menggunakan beberapa pola untuk melewati beberapa jenis nama file misalnya [0-9]*|*.bakuntuk melewati file kedua .bakfile , dan file yang tidak dimulai dengan angka.

zrajm
sumber
Doh! Ada bug (saya cocok *.txtbukan hanya *). Diperbaiki sekarang
zrajm
0

ini akan melakukannya tidak termasuk persis 'Musik'

cp -a ^'Music' /target

ini dan itu untuk mengecualikan hal-hal seperti Musik? * atau *? Musik

cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target
gabreal
sumber
The cphalaman manual pada MacOS memiliki -apilihan tetapi melakukan sesuatu yang sama sekali berbeda. Platform mana yang mendukung ini?
tripleee