Perilaku tak terduga dengan gema [[: digit:]]

10

Saya ingin bertanya:

Mengapa echo {1,2,3}diperluas ke 1 2 3 yang merupakan perilaku yang diharapkan, sementara echo [[:digit:]]kembali [[:digit:]]sementara saya berharap untuk mencetak semua digit dari 0hingga 9?

AbdAllah Talaat
sumber
Watch out for: unix.stackexchange.com/q/347950/117549
Jeff Schaller

Jawaban:

34

Karena mereka adalah dua hal yang berbeda. Ini {1,2,3}adalah contoh dari ekspansi brace . The {1,2,3}membangun diperluas oleh shell , sebelum echobahkan melihatnya. Anda dapat melihat apa yang terjadi jika Anda menggunakan set -x:

$ set -x
$ echo {1,2,3}
+ echo 1 2 3
1 2 3

Seperti yang Anda lihat, perintah echo {1,2,3}diperluas ke:

echo 1 2 3

Namun, [[:digit:]]adalah kelas karakter POSIX . Saat Anda memberikannya echo, shell juga memprosesnya terlebih dahulu, tapi kali ini ia diproses sebagai shell glob . ini bekerja dengan cara yang sama seperti jika Anda menjalankan echo *yang akan mencetak semua file di direktori saat ini. Tetapi [[:digit:]]apakah shell glob yang cocok dengan digit apa pun. Sekarang, dalam bash, jika shell glob tidak cocok dengan apa pun, itu akan diperluas ke dirinya sendiri:

$ echo /this*matches*no*files
+ echo '/this*matches*no*files'
/this*matches*no*files

Jika glob cocok dengan sesuatu, itu akan dicetak:

$ echo /e*c
+ echo /etc
/etc

Dalam kedua kasus, echohanya mencetak apa saja yang diperintahkan shell untuk dicetak, tetapi dalam kasus kedua, karena gumpalan cocok dengan sesuatu ( /etc) ia diminta untuk mencetak sesuatu itu.

Jadi, karena Anda tidak memiliki file atau direktori yang namanya persis terdiri dari satu digit (yang [[:digit:]]cocok dengan yang mana), gumpalan diperluas ke dirinya sendiri dan Anda mendapatkan:

$ echo [[:digit:]]
[[:digit:]]

Sekarang, coba buat file yang dipanggil 5dan jalankan perintah yang sama:

$ echo [[:digit:]]
5

Dan jika ada lebih dari satu file yang cocok:

$ touch 1 5       
$ echo [[:digit:]]
1 5

Ini (semacam) didokumentasikan dalam man bashpenjelasan nullglobopsi yang mematikan perilaku ini:

nullglob
    If  set,  bash allows patterns which match no files (see
    Pathname Expansion above) to expand to  a  null  string,
    rather than themselves.

Jika Anda mengatur opsi ini:

$ rm 1 5
$ shopt -s nullglob
$ echo [[:digit:]]  ## prints nothing

$ 
terdon
sumber
4
Lihat juga shopt -s failglobuntuk mendapatkan perilaku yang lebih bermanfaat mirip dengan kerang modern seperti zshatau fish.
Stéphane Chazelas
Saya setuju dengan Stéphane, gunakan failglob. nullglobdapat menyebabkan masalah yang tidak terduga, misalnya saat menempelkan URL yang kebetulan memiliki ?.
Kevin
1
Tentu, saya hanya menyebutkan nullglobuntuk menunjukkan bahwa polanya ditafsirkan sebagai gumpalan oleh shell.
terdon
14

{1,2,3}adalah brace ekspansi , itu memperluas ke kata-kata yang tercantum tanpa memperhatikan artinya.

[...]adalah grup karakter, digunakan dalam ekspansi nama file (atau wildcard, atau glob) mirip dengan tanda bintang *dan tanda tanya ?. Ini cocok dengan setiap karakter yang terdaftar di dalam, atau karakter yang merupakan anggota dari kelompok yang disebutkan seperti [:digit:]jika mereka terdaftar. Perilaku default kebanyakan shell adalah membiarkan wildcard apa adanya jika tidak ada file yang cocok dengannya.

(Perhatikan bahwa Anda tidak dapat benar-benar mengubah wildcard / pola menjadi rangkaian string yang cocok dengan itu. Tanda bintang dapat mencocokkan string apa pun dengan panjang apa pun, jadi jika memperluas pola apa pun yang berisinya akan menghasilkan daftar string yang tak terbatas.)

Begitu:

$ bash -c 'echo [[:digit:]]'           # bash leaves it as-is
[[:digit:]]
$ zsh -c 'echo [[:digit:]]'            # zsh by default complains if no match
zsh:1: no matches found: [[:digit:]]
$ touch 1 3 d i g t
$ bash -c 'echo [[:digit:]]'           # now there are two matches
1 3                                    # note that d, i, g and t do NOT match

Tetapi tetap saja:

$ bash -c 'echo {1,2,3}'
1 2 3

Keduanya diperluas oleh shell , tidak masalah jika perintah yang Anda jalankan adalah ls, atau echoatau rm. Juga perhatikan bahwa jika salah satu dari mereka dikutip, mereka tidak akan diperluas:

$ bash -c 'echo "[[:digit:]]"'         # even though matching files still exist
[[:digit:]]
$ bash -c 'echo "{1,2,3}"'
{1,2,3}
ilkkachu
sumber
terima kasih atas jawaban Anda, saya baru di linux jadi izinkan saya bertanya kepada Anda bagaimana gema terkait dengan file 1 3, fungsinya adalah untuk mencetak argumennya untuk stdout tidak mencari file seperti pengetahuan saya
AbdAllah Talaat
1
@AbdAllahTalaat ini tidak ada hubungannya dengan gema, sebenarnya. Shell (misalnya bash) akan "memperluas" [[:digit:]] sebelum melewatinya echo, jadi echotidak pernah melihat [[:digit:]], ia hanya melihat 1 3. Anda dapat melihat ini dalam tindakan dengan menjalankan set -xyang akan mencetak perintah yang sebenarnya sedang dijalankan (jalankan set +xuntuk mematikannya lagi).
terdon
@AbdAllahTalaat, echotidak mencari file, shell tidak, sebelum menjalankan echo.
ilkkachu
Terutama karena saya pikir dalam DOS / Windows utilitas memperluas wildcard, bukan shell. (Saya mungkin salah)
ilkkachu
maaf kawan, saya menggeser jawaban yang benar ke jawaban tedron karena komentarnya mengandung arti bahwa bash adalah apa yang tidak dikerjakan oleh pekerjaan ... jawabannya juga mengandung makna itu .. kalian semua membantu saya ... saya berharap jika saya bisa meletakkan jawaban yang benar untuk semua jawaban dan komentar Anda
AbdAllah Talaat
4

{1,2,3}(dan misalnya {1..3}adalah penjepit ekspansi . Mereka ditafsirkan oleh shell sebelum eksekusi perintah.

[[:digit:]]adalah token pencocokan pola , tetapi Anda tidak menggunakannya di lokasi dengan file apa pun yang cocok dengan pola itu. Jika Anda menggunakan kecocokan pola yang tidak memiliki kecocokan, itu berkembang dengan sendirinya:

$ echo [[:digit:]]; touch 3; echo [[:digit:]]
[[:digit:]]
3
DopeGhoti
sumber
Tidak, seperti yang ditunjukkan oleh jawaban lain dengan benar, polanya cocok dengan nama file.
Toby Speight