Bagaimana bash membedakan antara ekspansi brace dan pengelompokan perintah?

48

Saya perhatikan bahwa {dapat digunakan dalam ekspansi brace:

echo {1..8}

atau dalam pengelompokan perintah:

{ls;echo hi}

Bagaimana bash tahu bedanya?

lovespring
sumber
1
Pertanyaan bagus, +1. Sepertinya itu yang {ditafsirkan sebagai daftar perintah jika muncul di awal perintah dan sebagai penjepit ekspansi sebaliknya, tapi saya tidak yakin.
Celada
16
{ls;echo hi}tidak legal bash. Anda membutuhkan ruang setelah penjepit pembuka dan titik koma sebelum yang ditutup.
PSkocik

Jawaban:

39

Alasan disederhanakan adalah adanya satu karakter: space.

Perluasan Brace tidak memproses spasi (tidak dikutip).

Sebuah {...}daftar kebutuhan ruang (un-dikutip).

Jawaban yang lebih rinci adalah bagaimana shell mem-parsing baris perintah .


Langkah pertama untuk mem-parsing (mengerti) baris perintah adalah membaginya menjadi beberapa bagian.
Bagian-bagian ini (biasanya disebut kata atau token) dihasilkan dari pembagian baris perintah pada setiap meta-karakter dari tautan :

  1. Pisahkan perintah menjadi token yang dipisahkan oleh set meta-karakter: SPACE, TAB, NEWLINE,;, (,), <,>, |, dan &. Jenis token termasuk kata, kata kunci, pengalih I / O, dan titik koma.

Meta-karakter: spacetabenter;,<>|dan &.

Setelah dipisah, kata-kata mungkin bertipe (seperti dipahami oleh shell):

  • Pra-tugas perintah: LC=ALL ...
  • Perintah LC=ALL echo
  • Argumen LC=ALL echo "hello"
  • Pengalihan LC=ALL echo "hello" >&2

Ekspansi penjepit

Hanya jika "brace string" (tanpa spasi atau meta-karakter) adalah satu kata (seperti dijelaskan di atas) dan tidak dikutip , itu adalah kandidat untuk "Brace ekspansi". Pemeriksaan lebih lanjut dilakukan pada struktur internal nanti.

Dengan demikian, ini: {ls,-l}memenuhi syarat sebagai "ekspansi Brace" menjadi ls -l, baik sebagai first wordatau argument(dalam bash, zsh berbeda).

$ {ls,-l}            ### executes `ls -l`
$ echo {ls,-l}       ### prints `ls -l`

Tapi ini tidak akan: {ls ,-l}. Bash akan membagi spacedan menguraikan baris sebagai dua kata: {lsdan ,-l}yang akan memicu command not found(argumen ,-l}hilang):

 $ {ls ,-l}
 bash: {ls: command not found

Baris Anda: {ls;echo hi}tidak akan menjadi "Brace expansion" karena dua meta-karakter ;dan space.

Ini akan dipecah menjadi tiga bagian ini: {lsperintah baru: echo hi}. Memahami bahwa ;pemicu dimulainya perintah baru. Perintah {lstidak akan ditemukan, dan perintah berikutnya akan mencetak hi}:

$ {ls;echo hi}
bash: {ls: command not found
hi}

Jika ditempatkan setelah beberapa perintah lain, ia tetap akan memulai perintah baru setelah ;:

$ echo {ls;echo hi}
{ls
hi}

Daftar

Salah satu "perintah senyawa" adalah "Brace Daftar" (kata-kata saya): { list; }.
Seperti yang Anda lihat, itu didefinisikan dengan spasi dan penutup ;.
Ruang-ruang dan ;diperlukan karena keduanya {dan }" Kata-kata Cadangan ".

Dan karenanya, untuk dikenali sebagai kata-kata, harus dikelilingi oleh meta-karakter (hampir selalu:) space.

Seperti dijelaskan pada poin 2 dari halaman yang terhubung

  1. Periksa token pertama dari setiap perintah untuk melihat apakah itu ...., {, atau (, maka perintah tersebut sebenarnya adalah perintah majemuk.

Contoh Anda: {ls;echo hi}bukan daftar.

Perlu penutupan ;dan satu ruang (setidaknya) setelahnya {. Yang terakhir }ditentukan oleh penutup ;.

Ini daftar { ls;echo hi; }. Dan ini { ls;echo hi;}juga (kurang umum digunakan, tetapi valid) (Terima kasih @choroba atas bantuannya).

$ { ls;echo hi; }
A-list-of-files
hi

Tetapi sebagai argumen (shell tahu perbedaannya) terhadap suatu perintah, itu memicu kesalahan:

$ echo { ls;echo hi; }
bash: syntax error near unexpected token `}'

Tapi hati-hati dengan apa yang Anda yakini mengupas shell:

$ echo { ls;echo hi;
{ ls
hi

sumber
2
ini benar-benar jawaban terbaik, karena Anda memberi kami bagaimana bash parser bekerja! dan dengan penjelasan detail!
lovespring
2
Anda tidak perlu ruang antara ;dan }. { ls;}berfungsi sebagai titik koma sudah menjadi meta-karakter.
choroba
1
@lovespring Terima kasih, ya saya menginvestasikan waktu dalam menulisnya. Saya senang mengetahui bahwa ini berguna. Sekali lagi terima kasih.
artikel yang bagus, terima kasih banyak untuk referensi
Edward Torvalds
16

Blok {adalah kata kunci shell, sehingga harus dipisahkan dari kata berikutnya dengan spasi, sementara dalam ekspansi brace, tidak boleh ada ruang (jika Anda perlu memperkuat ruang, Anda harus menghindarinya:) echo {\ ,a}{b,c}.

Anda dapat menggunakan ekspansi brace di awal perintah:

{ls,.}  # expands to "ls ."

Anda tidak dapat menggunakannya untuk memperluas ke blok, karena penguraian perintah pengelompokan terjadi sebelum ekspansi:

echo {'{ ls','.;}'}  # { ls .;}
{'{ ls','.;}'}       # bash: { ls: No such file or directory
choroba
sumber
5

Ia tahu dengan memeriksa sintaks dari baris perintah. Dengan cara yang sama ia tahu bahwa dalam ekspresi echo echo, gema pertama harus diperlakukan sebagai perintah dan gema kedua sebagai parameter dari gema pertama.

Dalam bash itu sangat sederhana, karena { cmd; }harus memiliki spasi dan titik koma. Namun, misalnya di zsh mereka tidak diperlukan, tetapi masih dengan menganalisis konteks {}shell dapat mengetahui apa yang harus dilakukan dengan kontennya.

Pertimbangkan yang berikut ini:

alias 1..3=date
{ 1..3; }    #in bash
{1..3}       #in zsh

Keduanya mengembalikan tanggal saat ini, tetapi

echo {1..3}

kembali 1 2 3karena shell tahu {}dalam argumen untuk perintah echo, jadi harus diperluas.

jimmij
sumber
{diikuti oleh tanda kutip ruang tidak memulai ekspansi penjepit di bash.
choroba
@ choroba Ya, dan tidak hanya setelah itu {. Ruang yang tidak dikutip tidak dapat berada di mana saja karena shell membagi seluruh baris perintah pada spasi.
jimmij
0

Pertama, kata majemuk harus kata dengan sendirinya dan kata pertama dari baris perintah:

echo { these braces are just words }

Kedua, kawat gigi individu tidak istimewa (seperti yang Anda lihat di atas). Kawat gigi kosong juga tidak istimewa:

echo {} # just the token {}: familiar from the find command

Apa pun tanpa koma juga sendiri

echo {abc} # just {abc}

Di sinilah aksi dimulai.

echo {a,b} # braces disappear, a b results.

Jadi pada dasarnya untuk ekspansi brace untuk memulai, kita perlu satu kata (tidak dipisahkan menjadi bidang di spasi putih), di dalamnya ada setidaknya satu contoh dari {...}dalam yang ada setidaknya satu koma.

Ini bisa menjadi kata pertama di baris perintah, omong-omong:

{ls,-l} .   # just "ls -l ."
Kaz
sumber