Apa pengaturan dalam bash untuk globbing, untuk mengontrol apakah * cocok dengan file dot


12

Saya terkejut baru-baru ini ketika saya melakukan sesuatu seperti mv ./* ../somedirectory dan menemukan bahwa file seperti .gitignoretidak dipindahkan.

Saya melakukan sebagian besar pekerjaan saya di zsh pada OS X, dan kejutan ini menggigit saya di bash pada CentOS. Saya mencoba bash di OS X dan menemukan perilaku yang sama: *tidak cocok dengan file dot. Ini tampaknya sangat tidak diinginkan bagi saya, tetapi ternyata itu adalah default bash. (Ini mungkin juga merupakan default zsh untuk semua yang saya ingat, tapi saya mungkin telah mengubahnya bertahun-tahun yang lalu di .zshrc saya dan lupa itu pernah bekerja secara berbeda.)

Bagaimana saya bisa mengkonfigurasi bash untuk berperilaku seperti yang saya harapkan: untuk * untuk mencocokkan semua file, dan tidak mengabaikan file dot.

Dalam hal ini sama sekali tidak jelas, inilah cara mereproduksinya

cd /tmp
mkdir {t,d}est
touch test/{.,}{1,2,3,4,5,6,7}
ls -hal test
mv test/* dest
ls -hal test     # notice dot files are still there
ls -hal dest     # notice only some files were mv'ed


Ya, mereka terkait. Itu tidak muncul ketika saya mencari (mungkin karena penanya tidak menyebutkan globbing atau file dot), tapi itu benar-benar pertanyaan yang berbeda. Dia bertanya bagaimana cara memindahkan file, dan saya bertanya bagaimana mengubah perilaku shell, dan khususnya untuk bash. Saya mungkin tidak bertanya apakah pertanyaan itu muncul untuk saya (karena jawaban Anda yang sangat lengkap dan menyeluruh di sana termasuk pengaturan bash) tetapi itu masih merupakan pertanyaan yang berbeda, dan seseorang yang mencari jawaban untuk pertanyaan saya belum tentu akan menemukan pertanyaannya.
iconoclast

Seluruh "seseorang yang mencari jawaban untuk pertanyaan saya tidak selalu akan menemukan pertanyaannya" adalah bisnis yang tepat mengapa kami memiliki cara untuk menutup pertanyaan sebagai duplikat.
Gilles 'SANGAT berhenti menjadi jahat'

ya, tetapi Anda sepertinya kehilangan poin utama, yaitu pertanyaan yang berbeda .
iconoclast

Jawaban:


14

Pesta

Seperti yang sudah Anda perhatikan, bash tidak akan cocok dengan .di awal nama atau garis miring. Untuk mengubah yang cocok dengan titik Anda harus mengatur dotglobopsi - man bash :

dotglob Jika diset, bash menyertakan nama file yang diawali dengan `. ' di
    hasil perluasan pathname.

Untuk mengaktifkan / mengaturnya dengan penggunaan bash shopt, misalnya:

shopt -s dotglob


Untuk zsh Anda juga dapat menggunakan dotglobopsi tetapi tetapi Anda harus menggunakannya setoptuntuk mengaktifkannya, misalnya:

setopt dotglob

2
Dengan zsh, opsi yang lebih baik adalah mengaktifkan dotglob berdasarkan per-glob:mv test/*(D) /dest
Stéphane Chazelas

7

Saya menguji ini dan itu memecahkan masalah:

shopt -s dotglob

Keluaran:

~/stackexchangeanswers/40662$ ls -hal dest
total 8.0K
drwxr-xr-x 2 jodiec jodiec 4.0K 2012-06-12 22:15 .
drwxr-xr-x 4 jodiec jodiec 4.0K 2012-06-12 22:15 ..
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 1
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 .1
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 2
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 .2
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 3
-rw-r--r-- 1 jodiec jodiec    0 2012-06-12 22:15 .3
...snipped....

Maaf tapi Ulrich menjawab lebih dulu.
iconoclast

3
Kita semua berada di tim yang sama. Tidak apa-apa.
Jodie C

6

*adalah bola yang diperluas oleh shell. Secara default, shell tidak menyertakan file yang namanya dimulai dengan .(disebut file atau dotfile tersembunyi) kecuali jika yang .dimasukkan dimasukkan secara harfiah.

*atau [.]*atau ?*atau *.*atau dir/*tidak akan mencakup dotfiles.

.*atau dir/.*akan.

Jadi Anda bisa melakukan:

mv -- * .* /dest/

namun beberapa kerang termasuk bash(tetapi tidak zsh, mkshatau fish) memiliki misfeature bahwa ekspansi .*termasuk .dan ..entri direktori khusus, yang Anda tidak ingin di sini (dan umumnya tidak pernah ingin gumpal untuk memasukkan itulah sebabnya saya menyebutnya misfeature a).

Untuk alasan itu, Anda akan menemukan bahwa kadang-kadang orang menggunakan (dalam cangkang mirip Bourne):

mv -- * .[!.]* ..?* /dest/

Itu tiga gumpalan, yang pertama cocok dengan file yang tidak tersembunyi, yang kedua nama file dimulai dengan .diikuti oleh karakter selain .dan yang ketiga nama file dimulai dengan ..diikuti oleh setidaknya satu karakter.

Namun beberapa shell modern memiliki cara yang lebih baik

zsh

Dengan zsh, Anda dapat menggunakan (D)kualifikasi glob untuk menentukan bahwa glob harus menyertakan dotfile:

mv -- *(D) /dest/

zshjuga memperbaiki kesalahan lain dari shell Bourne di bahwa jika polanya tidak cocok, mvperintah tidak dijalankan.

Seperti dikatakan di atas juga tidak akan pernah termasuk .atau ..dalam gumpalannya, jadi

mv -- * .* /dest/

akan aman. Namun jika tidak ada file yang cocok *atau tidak ada file yang cocok .*dengan perintah akan dibatalkan, jadi akan lebih baik untuk menggunakan:

mv -- (*|.*) /dest/

Seperti pada beberapa shell lainnya, Anda juga dapat memaksa semua gumpalan untuk memasukkan dotfile (misalnya jika Anda mendapati dotfile ingin lebih sering dimasukkan daripada tidak) dengan:

setopt dotglob

atau:

set -o dotglob

Setelah itu, jika Anda ingin glob tertentu tidak menyertakan dotfiles, Anda dapat menulisnya:

echo *(^D)

Atau:

echo [^.]*

Pesta

Sayangnya bashtidak memiliki kualifikasi glob. Jadi, Anda harus mengaktifkan inklusi dotfile secara global. Dalam bash, sintaksnya adalah:

shopt -s dotglob

(dan gunakan [^.]*untuk gumpalan tanpa file tersembunyi).

Dengan dotglob, bashtidak termasuk .atau ..dalam gumpalan suka *, tetapi masih untuk gumpalan suka .*.

Jika Anda mengatur GLOBIGNOREvariabel ke sesuatu yang tidak kosong, maka secara otomatis mengaktifkan dotglobopsi dan mengecualikan .dan ..dari .*gumpalan tetapi tidak dari dir/.*atau .*/fileyang (!) Sehingga perlindungan cukup berguna. Anda bisa melakukannya GLOBIGNORE='*/.:*/..:./*:../*:*/./*:*/../*'tetapi kemudian akan merusak gumpalan seperti */.atau ./*atau ../*.

Cara lain yang lebih baik adalah menggunakan [.]*atau dir/[.]*atau [.]*/file(dengan dotglobdiaktifkan) untuk memperluas dotfile kecuali .dan ...

ikan

fishgumpalan tidak termasuk .juga ... Ketika tidak ada kecocokan, tergantung pada versinya, itu akan berfungsi seperti zsh(atau bash -o failglob) atau bash -o nullglob.

mv -- * .* /dest/

Akan berfungsi jika ada file yang tersembunyi dan tidak tersembunyi. Jika tidak, YMMV dan dengan beberapa versi, ia dapat memanggil mv -- /destjika tidak ada file sama sekali.

ksh93

Tidak ada kualifikasi glob di ksh93keduanya. Anda dapat memasukkan dotfile dalam gumpalan dengan:

FIGNORE='@(.|..)'

Bertentangan dengan bashitu GLOBIGNORE, itu dilakukan dengan benar dan juga memperbaiki masalah .*termasuk .dan ...

yash

yashmemiliki dot-globopsi ( set -o dot-glob), tetapi bertentangan dengan bash, ekspansi glob (bahkan *) termasuk .dan ..jadi sangat tidak berguna.

tcsh

set globdot

Bekerja seperti dalam bash, yaitu *menyertakan file titik kecuali .dan ..tetapi .*masih termasuk .dan ..(dan Anda dapat menggunakan [.]*untuk memperluas file tersembunyi kecuali .dan ..).


4

Cara termudah di bash untuk mencetak file titik yang cocok, abaikan .dan ..adalah:

$ GLOBIGNORE=/+/
$ printf '%s\n' *


Untuk mengetes:

$ cd tmp; mkdir empty; cd empty; touch {,.}{a..h}
$ GLOBIGNORE=/+/
$  ls *
a  .a  b  .b  c  .c  d  .d  e  .e  f  .f  g  .g  h  .h

Ini mencetak semua file dengan titik-titik atau tidak, dengan pengecualian .dan ...

Contoh Anda juga akan berfungsi dengan benar:

$ cd /tmp; mkdir {t,d}est; touch test/{,.}{a..h}
$ mv test/* dest/
$ ls -a test
.  ..
$ ls -a test/*
ls: cannot access test/*: No such file or directory

Kosong file yang penting.
File sistem . ..masih ada tetapi ekspansi shell (menggunakan *) tidak akan memasukkannya.

Dan direktori dest ination berisi semua file:

$ cd dest
$ ls -a *
a  .a  b  .b  c  .c  d  .d  e  .e  f  .f  g  .g  h  .h

Mengapa?

Ini berfungsi karena pengaturan GLOBIGNORE memiliki efek samping ini :

  • Set dotglob ( shopt -s dotglob) ke file matematika dot saat ekspansi.
  • Nama file . dan ..diabaikan ketika GLOBIGNORE diatur dan bukan nol.

Rincian di manual: LESS=+'/The GLOBIGNORE' man bash.

Kita juga perlu mengatur variabel GLOBIGNORE untuk memuat nama yang tidak cocok dengan nama file:
garis miring (dan sesuatu yang lain hanya sebagai tindakan pencegahan ganda).

$ GLOBIGNORE=/+/

Mungkin juga berguna untuk mengatur nullglob jika diperlukan untuk menghindari mendapatkan *ketika tidak ada file yang cocok dengan *.


0

Sangat tertarik dengan pengaturan GLOBIGNORE = / + / @ user79743 ini

Dari pengalaman saya unsetting dotglob adalah bisnis yang buruk untuk hampir semua perintah Anda (cp, mv dll) TAPI yang sama berlaku pengaturan yang nullglob (terutama dengan ekspresi reguler yang kemudian perlu melarikan diri!).

Namun demikian, jika Anda perlu mengaturnya di awal program Anda tetapi mengganggu bagian-bagian tertentu di mana Anda memanggil perintah seperti cp, mv dll. Atau menggunakan ekspresi reguler lakukan saja ini:

  # set dotglob, unset nullglob AFTER this
  is_nullglob=$( shopt -s | egrep -i '.*nullglob' )
  is_dotglob=$( shopt -s | egrep -i '.*dotglob' )
  [[ $is_nullglob ]] && shopt -u nullglob
  [[ ! $is_dotglob ]] && shopt -s dotglob
  # ... call commands ...
  # .....................
  # reset dotglob, nullglob to their previous values
  [[ $is_nullglob ]] && shopt -s nullglob
  [[ ! $is_dotglob ]] && shopt -u dotglob
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.