Bagaimana skrip parse Windows Command Interpreter (CMD.EXE)?

142

Saya berlari ke ss64.com yang memberikan bantuan yang baik mengenai bagaimana menulis skrip batch yang dijalankan oleh Windows Command Interpreter.

Namun, saya tidak dapat menemukan penjelasan yang baik tentang tata bahasa skrip batch, bagaimana hal-hal berkembang atau tidak berkembang, dan bagaimana cara melarikan diri.

Berikut adalah contoh pertanyaan yang belum dapat saya pecahkan:

  • Bagaimana sistem penawaran dikelola? Saya membuat skrip TinyPerl
    ( foreach $i (@ARGV) { print '*' . $i ; }), mengkompilasinya dan menyebutnya seperti ini:
    • my_script.exe "a ""b"" c" → outputnya adalah *a "b*c
    • my_script.exe """a b c""" → keluaran itu *"a*b*c"
  • Bagaimana cara echokerja perintah internal ? Apa yang diperluas di dalam perintah itu?
  • Mengapa saya harus menggunakan for [...] %%Iskrip file, tetapi for [...] %Idalam sesi interaktif?
  • Apa saja karakter pelariannya, dan dalam konteks apa? Bagaimana cara menghindari tanda persen? Misalnya, bagaimana saya bisa menggemakan%PROCESSOR_ARCHITECTURE% secara harfiah? Saya menemukan ituecho.exe %""PROCESSOR_ARCHITECTURE% berhasil, apakah ada solusi yang lebih baik?
  • Bagaimana pasang % cocok? Contoh:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • Bagaimana cara memastikan suatu variabel beralih ke perintah sebagai argumen tunggal jika variabel ini berisi tanda kutip ganda?
  • Bagaimana variabel disimpan saat menggunakan setperintah? Misalnya, jika saya lakukan set a=a" bdan kemudian echo.%a%saya peroleh a" b. Namun jika saya gunakan echo.exedari UnxUtils, saya mengerti a b. Bagaimana bisa %a%mengembang dengan cara yang berbeda?

Terima kasih untuk lampu anda

Benoit
sumber
Rob van der Woude memiliki skrip Batch yang mengagumkan dan rujukan prompt Perintah Windows di situsnya.
JBRWilkinson

Jawaban:

200

Kami melakukan percobaan untuk menyelidiki tata bahasa skrip batch. Kami juga menyelidiki perbedaan antara mode batch dan command line.

Parser Garis Batch:

Berikut ini adalah ikhtisar singkat fase dalam parser baris file batch:

Fase 0) Baca Baris:

Fase 1) Ekspansi Persen:

Fase 2) Memproses karakter khusus, tokenize, dan membangun blok perintah yang di-cache: Ini adalah proses kompleks yang dipengaruhi oleh hal-hal seperti kutipan, karakter khusus, pembatas token, dan lolos caret.

Fase 3) Gema perintah terurai hanya jika blok perintah tidak dimulai @, dan ECHO AKTIF pada awal langkah sebelumnya.

Fase 4) UNTUK %Xekspansi variabel: Hanya jika perintah FOR aktif dan perintah setelah DO sedang diproses.

Fase 5) Ekspansi Tertunda: Hanya jika ekspansi yang tertunda diaktifkan

Fase 5.3) Pemrosesan pipa: Hanya jika perintah ada di kedua sisi pipa

Fase 5.5) Jalankan Redirection:

Fase 6) Pemrosesan CALL / Penggandaan Caret: Hanya jika token perintahnya adalah CALL

Tahap 7) Jalankan: Perintah dijalankan


Berikut detail untuk setiap fase:

Perhatikan bahwa fase yang dijelaskan di bawah ini hanya model cara kerja pengurai batch. Internal cmd.exe yang sebenarnya mungkin tidak mencerminkan fase-fase ini. Tetapi model ini efektif untuk memprediksi perilaku skrip batch.

Fase 0) Jalur Baca : Baca jalur input terlebih dahulu <LF>.

  • Saat membaca baris yang akan diuraikan sebagai perintah, <Ctrl-Z>(0x1A) dibaca sebagai <LF>(LineFeed 0x0A)
  • Ketika GOTO atau CALL membaca baris saat memindai label: <Ctrl-Z>,, diperlakukan sebagai dirinya sendiri - itu tidak dikonversi menjadi<LF>

Fase 1) Ekspansi Persen:

  • Ganda %%digantikan oleh satu%
  • Perluasan argumen ( %*, %1,%2 , dll)
  • Perluasan %var% , jika var tidak ada ganti saja dengan apa-apa
  • Garis terpotong pada awalnya <LF>tidak dalam %var%ekspansi
  • Untuk penjelasan lengkap, baca paruh pertama ini dari dbenham Utas yang sama: Persen Fase

Fase 2) Memproses karakter khusus, tokenize, dan membangun blok perintah yang di-cache: Ini adalah proses kompleks yang dipengaruhi oleh hal-hal seperti kutipan, karakter khusus, pembatas token, dan lolos caret. Berikut ini adalah perkiraan proses ini.

Ada konsep yang penting di seluruh fase ini.

  • Token hanyalah serangkaian karakter yang diperlakukan sebagai satu unit.
  • Token dipisahkan oleh pembatas token. Pembatas token standar adalah <space> <tab> ; , = <0x0B> <0x0C>dan<0xFF>
    Pembatas token berurutan diperlakukan sebagai satu - tidak ada token kosong antara pembatas token
  • Tidak ada pembatas token dalam string yang dikutip. Seluruh string yang dikutip selalu diperlakukan sebagai bagian dari token tunggal. Token tunggal dapat terdiri dari kombinasi string yang dikutip dan karakter yang tidak dikutip.

Karakter berikut mungkin memiliki arti khusus dalam fase ini, tergantung pada konteks: <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

Lihatlah setiap karakter dari kiri ke kanan:

  • Jika <CR>kemudian menghapusnya, seolah-olah itu tidak pernah ada (kecuali untuk perilaku pengalihan aneh )
  • Jika sebuah tanda sisipan ( ^), karakter berikutnya akan keluar, dan tanda tanda untuk melarikan diri dihapus. Karakter yang lolos kehilangan semua makna khusus (kecuali untuk <LF>).
  • Jika kutipan ( "), ganti bendera tanda kutip. Jika tanda kutip aktif, maka hanya "dan<LF> istimewa. Semua karakter lain kehilangan makna khusus mereka sampai kutipan berikutnya mematikan bendera kutipan. Tidak mungkin untuk menghindari kutipan penutup. Semua karakter yang dikutip selalu dalam token yang sama.
  • <LF>selalu mematikan bendera kutipan. Perilaku lain bervariasi tergantung pada konteks, tetapi kutipan tidak pernah mengubah perilaku <LF>.
    • Lolos <LF>
      • <LF> dilucuti
      • Karakter selanjutnya lolos. Jika pada akhir buffer baris, maka baris berikutnya dibaca dan diproses oleh fase 1 dan 1.5 dan ditambahkan ke yang sekarang sebelum keluar dari karakter berikutnya. Jika karakter selanjutnya adalah <LF>, maka itu diperlakukan sebagai literal, artinya proses ini tidak rekursif.
    • Tidak terhindar dari <LF>tanda kurung
      • <LF> dilucuti dan penguraian garis saat ini dihentikan.
      • Setiap karakter yang tersisa di buffer baris diabaikan saja.
    • Tidak terhapus di <LF>dalam blok yang di-tanda kurung dalam IN
      • <LF> dikonversi menjadi <space>
      • Jika pada akhir buffer baris, maka baris berikutnya dibaca dan ditambahkan ke yang sekarang.
    • Tidak terhapus di <LF>dalam blok perintah yang di-kurung
      • <LF>dikonversi menjadi <LF><space>, dan <space>diperlakukan sebagai bagian dari baris berikutnya dari blok perintah.
      • Jika pada akhir buffer baris, maka baris berikutnya dibaca dan ditambahkan ke spasi.
  • Jika salah satu karakter khusus & | <atau >, pisahkan garis pada titik ini untuk menangani pipa, menyatukan perintah, dan pengalihan.
    • Dalam kasus pipa ( |), setiap sisi adalah perintah terpisah (atau blok perintah) yang mendapat penanganan khusus dalam fase 5.3
    • Dalam kasus &, &&atau ||Rangkaian perintah, setiap sisi Rangkaian diperlakukan sebagai perintah yang terpisah.
    • Dalam kasus <, <<, >, atau >>pengalihan, pengalihan klausa diurai, sementara dihapus, dan kemudian ditambahkan ke akhir dari perintah saat ini. Klausa pengalihan terdiri dari digit pegangan file opsional, operator pengalihan, dan token tujuan pengalihan.
      • Jika token yang mendahului operator redirection adalah digit unescaped tunggal, maka digit menentukan pegangan file yang akan diarahkan. Jika token pegangan tidak ditemukan, maka output redirection default ke 1 (stdout), dan input redirection default ke 0 (stdin).
  • Jika token pertama untuk perintah ini (sebelum memindahkan redirection ke akhir) dimulai dengan @, maka @memiliki arti khusus. ( @tidak spesial dalam konteks lain)
    • Spesial @dihapus.
    • Jika ECHO AKTIF, maka perintah ini, bersama dengan perintah gabungan berikut di baris ini, dikecualikan dari gema fase 3. Jika @sebelum pembukaan (, maka seluruh blok yang dikurung dikecualikan dari gema fase 3.
  • Tanda kurung proses (memberikan pernyataan majemuk melintasi beberapa baris):
    • Jika parser tidak mencari token perintah, maka (tidak khusus.
    • Jika parser sedang mencari token perintah dan menemukan (, kemudian mulai pernyataan majemuk baru dan menambah penghitung tanda kurung
    • Jika penghitung tanda kurung> 0 maka )akhiri pernyataan majemuk dan kurangi penghitung tanda kurung.
    • Jika ujung garis tercapai dan penghitung tanda kurung> 0 maka baris berikutnya akan ditambahkan ke pernyataan gabungan (dimulai lagi dengan fase 0)
    • Jika penghitung tanda kurung adalah 0 dan parser sedang mencari perintah, maka )fungsinya mirip dengan REMpernyataan selama itu segera diikuti oleh pembatas token, karakter khusus, baris baru, atau akhir file
      • Semua karakter khusus kehilangan artinya kecuali ^(penggabungan baris dimungkinkan)
      • Setelah akhir baris logis tercapai, seluruh "perintah" dibuang.
  • Setiap perintah diuraikan menjadi serangkaian token. Token pertama selalu diperlakukan sebagai token perintah (setelah khusus @telah dilucuti dan pengalihan dipindahkan ke akhir).
    • Pembatas token terkemuka sebelum token perintah dilucuti
    • Saat parsing token perintah, (berfungsi sebagai pembatas token perintah, selain sebagai pembatas token standar
    • Penanganan token berikutnya tergantung pada perintah.
  • Sebagian besar perintah hanya menggabungkan semua argumen setelah token perintah menjadi token argumen tunggal. Semua pembatas token argumen dipertahankan. Opsi argumen biasanya tidak diuraikan sampai fase 7.
  • Tiga perintah mendapatkan penanganan khusus - IF, FOR, dan REM
    • JIKA dibagi menjadi dua atau tiga bagian berbeda yang diproses secara independen. Kesalahan sintaksis dalam konstruksi IF akan menghasilkan kesalahan sintaksis fatal.
      • Operasi perbandingan adalah perintah aktual yang mengalir hingga fase 7
        • Semua opsi IF sepenuhnya diuraikan dalam fase 2.
        • Pembatas token berturut-turut runtuh ke dalam satu ruang.
        • Bergantung pada operator pembanding, akan ada satu atau dua token nilai yang diidentifikasi.
      • Blok perintah True adalah kumpulan perintah setelah kondisi, dan diuraikan seperti blok perintah lainnya. Jika ELSE akan digunakan, maka blok True harus di-kurung.
      • Blok perintah False opsional adalah serangkaian perintah setelah ELSE. Sekali lagi, blok perintah ini diuraikan secara normal.
      • Blok perintah Benar dan Salah tidak secara otomatis mengalir ke fase berikutnya. Proses selanjutnya mereka dikendalikan oleh fase 7.
    • FOR dibagi menjadi dua setelah DO. Kesalahan sintaksis dalam konstruksi FOR akan menghasilkan kesalahan sintaksis yang fatal.
      • Bagian melalui DO adalah perintah UNTUK iterasi aktual yang mengalir sepanjang fase 7
        • Semua opsi UNTUK sepenuhnya diuraikan dalam fase 2.
        • Klausa IN tanda kurung memperlakukan <LF>sebagai <space>. Setelah klausa IN diuraikan, semua token digabungkan bersama untuk membentuk token tunggal.
        • Pembatas token yang tidak terhapus / tidak dikutip secara otomatis runtuh ke dalam satu ruang di seluruh perintah FOR melalui DO.
      • Bagian setelah DO adalah blok perintah yang diurai secara normal. Pemrosesan selanjutnya dari blok perintah DO dikendalikan oleh iterasi pada fase 7.
    • REM terdeteksi dalam fase 2 diperlakukan secara dramatis berbeda dari semua perintah lainnya.
      • Hanya satu token argumen yang diuraikan - parser mengabaikan karakter setelah token argumen pertama.
      • Perintah REM mungkin muncul dalam output fase 3, tetapi perintah itu tidak pernah dieksekusi, dan teks argumen asli digaungkan - lolos dari caret tidak dihapus, kecuali ...
        • Jika hanya ada satu token argumen yang berakhir dengan unescaped ^yang mengakhiri baris, maka token argumen dibuang, dan baris berikutnya diuraikan dan ditambahkan ke REM. Ini berulang sampai ada lebih dari satu token, atau karakter terakhir tidak ^.
  • Jika token perintah dimulai dengan :, dan ini adalah babak pertama fase 2 (bukan restart karena CALL di fase 6) maka
    • Token biasanya diperlakukan sebagai Label Tidak Eksekusi .
      • Sisa dari garis-parsing, namun ), <, >, &dan |tidak lagi memiliki arti khusus. Seluruh sisa baris dianggap sebagai bagian dari label "perintah".
      • The ^terus menjadi istimewa, yang berarti bahwa garis kelanjutan dapat digunakan untuk menambahkan baris berikutnya untuk label.
      • Sebuah Label unexecuted dalam blok kurung akan menghasilkan kesalahan sintaks yang fatal kecuali segera diikuti dengan perintah atau dieksekusi Label pada baris berikutnya.
        • (tidak lagi memiliki arti khusus untuk perintah pertama yang mengikuti Label Tidak Eksekusi .
      • Perintah dibatalkan setelah penguraian label selesai. Fase berikutnya tidak terjadi untuk label
    • Ada tiga pengecualian yang dapat menyebabkan label yang ditemukan di fase 2 diperlakukan sebagai Label yang Dieksekusi yang terus diurai melalui fase 7.
      • Ada pengalihan yang mendahului label token, dan ada |pipa atau &, &&atau ||perintah Rangkaian pada baris.
      • Ada pengalihan yang mendahului token label, dan perintah berada di dalam blok yang di-kurung.
      • Token label adalah perintah pertama pada baris di dalam blok yang di-kurung, dan baris di atas berakhir dengan Label yang Tidak Dieksekusi .
    • Berikut ini terjadi ketika Label Eksekusi ditemukan dalam fase 2
      • Label, argumennya, dan pengalihannya semua dikecualikan dari output gema dalam fase 3
      • Setiap perintah berikutnya yang digabungkan pada baris sepenuhnya diurai dan dieksekusi.
    • Untuk informasi lebih lanjut tentang Label Dieksekusi vs Label tidak dijalankan , lihat https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Fase 3) Gema perintah terurai hanya jika blok perintah tidak dimulai @, dan ECHO AKTIF pada awal langkah sebelumnya.

Fase 4) UNTUK %Xekspansi variabel: Hanya jika perintah FOR aktif dan perintah setelah DO sedang diproses.

  • Pada titik ini, fase 1 pemrosesan batch akan telah mengubah variabel FOR seperti %%Xmenjadi %X. Baris perintah memiliki aturan ekspansi persen berbeda untuk fase 1. Ini adalah alasan bahwa baris perintah menggunakan %Xtetapi file batch digunakan %%Xuntuk variabel FOR.
  • UNTUK nama variabel adalah case sensitive, tetapi ~modifierstidak case case.
  • ~modifiersdidahulukan dari nama variabel. Jika karakter berikut ~adalah pengubah dan nama variabel UNTUK yang valid, dan ada karakter berikutnya yang merupakan nama variabel UNTUK yang aktif, maka karakter tersebut diartikan sebagai pengubah.
  • UNTUK nama variabel bersifat global, tetapi hanya dalam konteks klausa DO. Jika sebuah rutin PANGGILAN dari dalam klausa DO DO, maka variabel UNTUK tidak diperluas dalam rutin PANGGILAN. Tetapi jika rutin memiliki perintah FOR sendiri, maka semua variabel FOR saat ini didefinisikan dapat diakses oleh perintah DO dalam.
  • UNTUK nama variabel dapat digunakan kembali dalam FOR yang bersarang. Nilai FOR bagian dalam didahulukan, tetapi begitu INNER FOR ditutup, maka nilai FOR bagian luar dipulihkan.
  • Jika ECHO AKTIF pada awal fase ini, maka fase 3) diulang untuk menunjukkan perintah DO yang diuraikan setelah variabel UNTUK telah diperluas.

---- Dari titik ini dan seterusnya, setiap perintah yang diidentifikasi dalam fase 2 diproses secara terpisah.
---- Fase 5 hingga 7 diselesaikan untuk satu perintah sebelum pindah ke yang berikutnya.

Fase 5) Ekspansi Tertunda: Hanya jika ekspansi yang tertunda aktif, perintah tidak ada dalam tanda kurung di kedua sisi pipa , dan perintah itu bukan skrip batch "telanjang" (nama skrip tanpa tanda kurung, CALL, gabungan perintah, atau pipa).

  • Setiap token untuk perintah diuraikan untuk ekspansi yang tertunda secara independen.
    • Kebanyakan perintah mem-parsing dua atau lebih token - token perintah, token argumen, dan masing-masing token tujuan pengalihan.
    • Perintah FOR mem-parsing token klausa IN saja.
    • Perintah IF mem-parsing nilai perbandingan hanya - satu atau dua, tergantung pada operator perbandingan.
  • Untuk setiap token yang diuraikan, periksa dulu apakah itu berisi !. Jika tidak, maka token tidak diuraikan - penting untuk ^karakter. Jika tokennya berisi !, maka pindai setiap karakter dari kiri ke kanan:
    • Jika itu adalah tanda sisipan ( ^) karakter selanjutnya tidak memiliki arti khusus, tanda sisipan itu sendiri dihapus
    • Jika itu adalah tanda seru, cari tanda seru berikutnya (tanda sisipan tidak diamati lagi), perluas nilai variabel.
      • Pembukaan berturut-turut !diciutkan menjadi satu!
      • Sisa yang tidak berpasangan !dihapus
    • Memperluas vars pada tahap ini adalah "aman", karena karakter khusus tidak terdeteksi lagi (genap <CR>atau <LF>)
    • Untuk penjelasan yang lebih lengkap, bacalah bagian kedua dari utas yang sama dari dbenham - Exclamation Point Phase

Fase 5.3) Pemrosesan pipa: Hanya jika perintah ada di kedua sisi pipa
Setiap sisi pipa diproses secara independen dan tidak sinkron.

  • Jika perintah internal ke cmd.exe, atau itu adalah file batch, atau jika itu adalah blok perintah yang disisipkan, maka dieksekusi di thread cmd.exe baru melalui %comspec% /S /D /c" commandBlock", jadi blok perintah mendapat fase restart, tapi kali ini dalam mode baris perintah.
    • Jika blok perintah di-kurung, maka semua <LF>dengan perintah sebelum dan sesudah dikonversi ke <space>&. Lainnya <LF>dilucuti.
  • Ini adalah akhir dari pemrosesan untuk perintah pipa.
  • Lihat Mengapa ekspansi tertunda gagal ketika di dalam blok kode pipa? untuk lebih lanjut tentang penguraian dan pemrosesan pipa

Fase 5.5) Eksekusi Pengalihan: Setiap pengalihan yang ditemukan dalam fase 2 sekarang dieksekusi.

Fase 6) Pemrosesan CALL / Penggandaan Caret: Hanya jika token perintahnya adalah CALL, atau jika teks sebelum pembatas token standar yang pertama kali muncul adalah CALL. Jika CALL diuraikan dari token perintah yang lebih besar, maka bagian yang tidak digunakan akan ditambahkan ke token argumen sebelum melanjutkan.

  • Pindai token argumen untuk tanda kutip /?. Jika ditemukan di mana saja di dalam token, batalkan fase 6 dan lanjutkan ke Tahap 7, di mana BANTUAN untuk CALL akan dicetak.
  • Hapus yang pertama CALL, sehingga beberapa PANGGILAN dapat ditumpuk
  • Gandakan semua karat
  • Mulai ulang fase 1, 1.5, dan 2, tetapi jangan melanjutkan ke fase 3
    • Setiap dua kali lipat karat dikurangi kembali menjadi satu karat sepanjang mereka tidak dikutip. Namun sayangnya, kutipan caret tetap berlipat ganda.
    • Fase 1 sedikit berubah
      • Kesalahan ekspansi dalam langkah 1.2 atau 1.3 batalkan CALL, tetapi kesalahan itu tidak fatal - pemrosesan batch berlanjut.
    • Tugas fase 2 sedikit diubah
      • Setiap pengarahan ulang tanpa tanda kutip yang baru muncul dan tidak terdeteksi yang tidak terdeteksi pada putaran pertama fase 2 terdeteksi, tetapi dihapus (termasuk nama file) tanpa benar-benar melakukan pengalihan tersebut.
      • Setiap tanda tanda kutip tanpa tanda kutip yang baru muncul dan tidak terhapus di akhir baris dihapus tanpa melakukan kelanjutan garis
      • PANGGILAN dibatalkan tanpa kesalahan jika salah satu dari yang berikut terdeteksi
        • Baru muncul tanpa kutip, tidak terhapus &atau|
        • Token perintah yang dihasilkan dimulai dengan tanda kutip, unescaped (
        • Token pertama setelah CALL yang dihapus dimulai dengan @
      • Jika perintah yang dihasilkan adalah IF atau FOR yang tampaknya valid, maka eksekusi selanjutnya akan gagal dengan kesalahan yang menyatakan bahwa IFatau FORtidak diakui sebagai perintah internal atau eksternal.
      • Tentu saja PANGGILAN tidak dibatalkan dalam putaran 2 fase 2 ini jika token perintah yang dihasilkan adalah label yang dimulai dengan :.
  • Jika token perintah yang dihasilkan adalah PANGGILAN, maka restart Fase 6 (ulangi sampai tidak ada lagi PANGGILAN)
  • Jika token perintah yang dihasilkan adalah skrip batch atau label:, maka eksekusi CALL sepenuhnya ditangani oleh sisa Fase 6.
    • Dorong posisi file skrip batch saat ini di tumpukan panggilan sehingga eksekusi dapat dilanjutkan dari posisi yang benar ketika CALL selesai.
    • Siapkan token argumen% 0,% 1,% 2, ...% N dan% * untuk CALL, menggunakan semua token yang dihasilkan
    • Jika token perintah adalah label yang dimulai dengan :, maka
      • Mulai ulang Fase 5. Hal ini dapat memengaruhi apa: label ditelepon. Tetapi karena% 0 dll. Token sudah diatur, itu tidak akan mengubah argumen yang diteruskan ke rutin yang ditelepon.
      • Jalankan label GOTO untuk memposisikan penunjuk file di awal subrutin (abaikan token lain yang mungkin mengikuti: label) Lihat Tahap 7 untuk aturan tentang cara kerja GOTO.
    • Lain kontrol transfer ke skrip batch yang ditentukan.
    • Eksekusi dari CALLed: label atau script berlanjut sampai EXIT / B atau end-of-file tercapai, pada saat mana stack CALL muncul dan eksekusi kembali dari posisi file yang disimpan.
      Fase 7 tidak dijalankan untuk skrip yang ditelepon atau: label.
  • Jika tidak, hasil fase 6 akan masuk ke fase 7 untuk dieksekusi.

Tahap 7) Jalankan: Perintah dijalankan

  • 7.1 - Jalankan perintah internal - Jika token perintah dikutip, lewati langkah ini. Kalau tidak, cobalah untuk menguraikan perintah internal dan mengeksekusi.
    • Tes berikut dilakukan untuk menentukan apakah token perintah yang tidak dikutip mewakili perintah internal:
      • Jika token perintah sama persis dengan perintah internal, maka jalankan.
      • Lain memecahkan token perintah sebelum kejadian pertama + / [ ] <space> <tab> , ;atau =
        Jika teks sebelumnya adalah perintah internal, maka ingat perintah itu
        • Jika dalam mode baris perintah, atau jika perintah berasal dari blok yang dikurung, JIKA blok perintah benar atau salah, blok perintah FOR DO, atau terlibat dengan perintah gabungan, kemudian jalankan perintah internal
        • Lain (harus berupa perintah yang berdiri sendiri dalam mode batch) memindai folder saat ini dan PATH untuk file .COM, .EXE, .BAT, atau .CMD yang nama dasarnya cocok dengan token perintah asli
          • Jika file yang cocok pertama adalah .BAT atau .CMD, maka goto 7.3.exec dan jalankan skrip itu
          • Lain (pertandingan tidak ditemukan atau pertandingan pertama adalah .EXE atau .COM) jalankan perintah internal yang diingat
      • Jika tidak, pecahkan token perintah sebelum kemunculan pertama . \atau :
        Jika teks sebelumnya bukan perintah internal, maka pergi ke 7.2.
        Jika tidak, teks sebelumnya mungkin merupakan perintah internal. Ingat perintah ini.
      • Break token perintah sebelum kejadian pertama + / [ ] <space> <tab> , ;atau =
        Jika teks sebelumnya adalah path ke file yang sudah ada, maka goto 7.2
        Else melaksanakan perintah internal yang diingat.
    • Jika perintah internal diuraikan dari token perintah yang lebih besar, maka bagian token perintah yang tidak digunakan termasuk dalam daftar argumen
    • Hanya karena token perintah diuraikan sebagai perintah internal tidak berarti itu akan berhasil dijalankan. Setiap perintah internal memiliki aturan sendiri tentang bagaimana argumen dan opsi diuraikan, dan sintaks apa yang diizinkan.
    • Semua perintah internal akan mencetak bantuan alih-alih menjalankan fungsinya jika /?terdeteksi. Kebanyakan mengenali /?jika itu muncul di mana saja dalam argumen. Tetapi beberapa perintah seperti ECHO dan SET hanya mencetak bantuan jika token argumen pertama dimulai /?.
    • SET memiliki beberapa semantik yang menarik:
      • Jika perintah SET memiliki kutipan sebelum nama variabel dan ekstensi diaktifkan
        set "name=content" ignored -> value = content
        maka teks antara tanda sama dengan pertama dan kutipan terakhir digunakan sebagai konten (sama dengan kutipan pertama dan terakhir dikecualikan). Teks setelah kutipan terakhir diabaikan. Jika tidak ada kutipan setelah tanda sama dengan, maka sisa baris digunakan sebagai konten.
      • Jika perintah SET tidak memiliki kutipan di depan nama
        set name="content" not ignored -> nilai = "content" not ignored
        maka seluruh sisa baris setelah sama digunakan sebagai konten, termasuk setiap dan semua kutipan yang mungkin ada.
    • Perbandingan IF dievaluasi, dan tergantung pada apakah kondisinya benar atau salah, blok perintah dependen yang sudah diuraikan diproses, dimulai dengan fase 5.
    • Klausa IN dari perintah FOR diulang dengan tepat.
      • Jika ini adalah FOR / F yang mengulang output dari blok perintah, maka:
        • Klausa IN dieksekusi dalam proses cmd.exe baru melalui CMD / C.
        • Blok perintah harus melalui seluruh proses parsing untuk kedua kalinya, tetapi kali ini dalam konteks baris perintah
        • ECHO akan mulai ON, dan ekspansi yang tertunda biasanya akan mulai dinonaktifkan (tergantung pada pengaturan registri)
        • Semua perubahan lingkungan yang dibuat oleh blok perintah klausa IN akan hilang setelah proses cmd.exe anak berakhir
      • Untuk setiap iterasi:
        • Nilai variabel FOR didefinisikan
        • Blok perintah DO yang sudah diuraikan kemudian diproses, dimulai dengan fase 4.
    • GOTO menggunakan logika berikut untuk menemukan: label
      • Label diuraikan dari token argumen pertama
      • Script dipindai untuk kemunculan label berikutnya
        • Pemindaian dimulai dari posisi file saat ini
        • Jika akhir file tercapai, maka pemindaian akan kembali ke awal file dan berlanjut ke titik awal yang asli.
      • Pemindaian berhenti pada kemunculan pertama label yang ditemukannya, dan penunjuk file diatur ke garis segera setelah label. Eksekusi naskah dilanjutkan dari titik itu. Perhatikan bahwa GOTO sejati yang berhasil akan segera membatalkan blok kode yang diuraikan, termasuk loop FOR.
      • Jika label tidak ditemukan, atau token label hilang, maka GOTO gagal, pesan kesalahan dicetak, dan tumpukan panggilan muncul. Ini berfungsi secara efektif sebagai EXIT / B, kecuali perintah yang sudah diuraikan dalam blok perintah saat ini yang mengikuti GOTO masih dieksekusi, tetapi dalam konteks Penelpon (konteks yang ada setelah EXIT / B)
      • Lihat https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 untuk deskripsi yang lebih tepat tentang aturan yang digunakan untuk mengurai label.
    • RENAME dan COPY menerima wildcard untuk jalur sumber dan target. Tetapi Microsoft melakukan pekerjaan yang mengerikan dengan mendokumentasikan cara kerja wildcard, terutama untuk jalur target. Serangkaian aturan wildcard yang berguna dapat ditemukan di Bagaimana perintah Windows RENAME mengartikan wildcard?
  • 7.2 - Jalankan perubahan volume - Lain-lain jika token perintah tidak dimulai dengan kutipan, panjangnya tepat dua karakter, dan karakter ke-2 adalah titik dua, kemudian ubah volume
    • Semua token argumen diabaikan
    • Jika volume yang ditentukan oleh karakter pertama tidak dapat ditemukan, maka batalkan dengan kesalahan
    • Token perintah ::akan selalu menghasilkan kesalahan kecuali SUBST digunakan untuk menentukan volume untuk. ::
      Jika SUBST digunakan untuk menentukan volume ::, maka volume akan diubah, itu tidak akan diperlakukan sebagai label.
  • 7.3 - Menjalankan perintah eksternal - Lain-lain mencoba memperlakukan perintah sebagai perintah eksternal.
    • Jika dalam mode baris perintah dan perintah tidak dikutip dan tidak dimulai dengan spesifikasi volume, white-space, ,, ;, =atau +kemudian istirahat perintah tanda pada kejadian pertama <space> , ;atau =dan tambahkan sisa argumen token (s).
    • Jika karakter 2 dari token perintah adalah titik dua, maka verifikasi volume yang ditentukan oleh karakter 1 dapat ditemukan.
      Jika volume tidak dapat ditemukan, maka batalkan dengan kesalahan.
    • Jika dalam mode batch dan token perintah dimulai dengan :, maka goto 7.4
      Perhatikan bahwa jika token label dimulai dengan ::, maka ini tidak akan tercapai karena langkah sebelumnya akan dibatalkan dengan kesalahan kecuali SUBST digunakan untuk menentukan volume untuk ::.
    • Identifikasi perintah eksternal untuk dieksekusi.
      • Ini adalah proses kompleks yang mungkin melibatkan volume saat ini, direktori saat ini, variabel PATH, variabel PATHEXT, dan atau asosiasi file.
      • Jika perintah eksternal yang valid tidak dapat diidentifikasi, maka batalkan dengan kesalahan.
    • Jika dalam mode baris perintah dan token perintah dimulai dengan :, maka goto 7.4
      Perhatikan bahwa ini jarang tercapai karena langkah sebelumnya akan dibatalkan dengan kesalahan kecuali jika token perintah dimulai dengan ::, dan SUBST digunakan untuk menentukan volume untuk:: , dan Seluruh token perintah adalah jalur yang valid ke perintah eksternal.
    • 7.3.exec - Jalankan perintah eksternal.
  • 7.4 - Abaikan label - Abaikan perintah dan semua argumennya jika token perintah dimulai :.
    Aturan dalam 7.2 dan 7.3 dapat mencegah label mencapai titik ini.

Parser Baris Perintah:

Bekerja seperti BatchLine-Parser, kecuali:

Fase 1) Ekspansi Persen:

  • Tidak %*, %1dll. Perluasan argumen
  • Jika var tidak terdefinisi, maka %var%dibiarkan tidak berubah.
  • Tidak ada penanganan khusus %%. Jika var = konten, maka %%var%%perluas %content%.

Fase 3) Gema perintah yang diuraikan

  • Ini tidak dilakukan setelah fase 2. Ini hanya dilakukan setelah fase 4 untuk blok perintah FOR DO.

Fase 5) Ekspansi Tertunda: hanya jika Ekspansi Tertunda diaktifkan

  • Jika var tidak terdefinisi, maka !var!dibiarkan tidak berubah.

Fase 7) Jalankan Perintah

  • Upaya untuk CALL atau GOTO a: label mengakibatkan kesalahan.
  • Seperti yang sudah didokumentasikan dalam fase 7, label yang dieksekusi dapat mengakibatkan kesalahan di bawah skenario yang berbeda.
    • Label yang dijalankan batch hanya dapat menyebabkan kesalahan jika dimulai dengan ::
    • Label baris perintah yang dijalankan hampir selalu menghasilkan kesalahan

Parsing dari nilai integer

Ada banyak konteks berbeda di mana cmd.exe mem-parsing nilai integer dari string, dan aturannya tidak konsisten:

  • SET /A
  • IF
  • %var:~n,m% (ekspansi substring variabel)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

Detail untuk aturan ini dapat ditemukan di Aturan untuk bagaimana CMD.EXE mem-parsing angka


Bagi siapa pun yang ingin meningkatkan aturan parsing cmd.exe, ada topik diskusi di forum DosTips di mana masalah dapat dilaporkan dan saran dibuat.

Semoga membantu
Jan Erik (jeb) - Penulis asli dan penemu fase
Dave Benham (dbenham) - Banyak konten tambahan dan pengeditan

dbenham
sumber
4
Halo jeb, terima kasih atas wawasan Anda ... Mungkin sulit dimengerti, tapi saya akan mencoba memikirkannya! Anda tampaknya telah melakukan banyak tes! Terima kasih telah menerjemahkan ( administrator.de/… )
Benoit
2
Gelombang batch 5) - %% a akan telah diubah menjadi% a pada Tahap 1, jadi ekspansi for-loop benar-benar memperluas% a. Juga, saya menambahkan penjelasan yang lebih rinci tentang Batch fase 1 dalam jawaban di bawah ini (saya tidak punya hak istimewa mengedit)
dbenham
3
Jeb - mungkin fase 0 bisa dipindahkan dan dikombinasikan dengan fase 6? Itu lebih masuk akal bagi saya, atau adakah alasan mengapa mereka dipisahkan seperti itu?
dbenham
1
@aschipfl - Saya memperbarui bagian itu. Fungsi yang )benar - benar berfungsi hampir seperti REMperintah ketika penghitung tanda kurung adalah 0. Coba keduanya dari baris perintah ) Ignore thisecho OK & ) Ignore this
:,
1
@aschipfl ya itu benar, oleh karena itu terkadang Anda melihat 'set "var =% expr%"! 'tanda seru terakhir akan dihapus tetapi memaksa fase 5
jeb
62

Saat memanggil perintah dari jendela perintah, tokenisasi argumen baris perintah tidak dilakukan oleh cmd.exe(alias "shell"). Paling sering tokenization dilakukan oleh runtime C / C ++ proses yang baru terbentuk, tetapi ini tidak selalu demikian - misalnya, jika proses baru tidak ditulis dalam C / C ++, atau jika proses baru memilih untuk mengabaikan argvdan memproses commandline mentah untuk dirinya sendiri (misalnya dengan GetCommandLine ()). Pada level OS, Windows melewati baris perintah yang tidak di-stringed sebagai string tunggal ke proses baru. Ini berbeda dengan kebanyakan shell * nix, di mana shell tokenizes argumen dengan cara yang konsisten dan dapat diprediksi sebelum meneruskannya ke proses yang baru dibentuk. Semua ini berarti bahwa Anda mungkin mengalami perilaku tokenisasi argumen yang sangat berbeda di berbagai program di Windows, karena masing-masing program sering mengambil tokenisasi argumen ke tangan mereka sendiri.

Jika kedengarannya seperti anarki, itu semacam. Namun, karena sejumlah besar program Windows memang memanfaatkan runtime Microsoft C / C ++ argv, mungkin berguna untuk memahami bagaimana MSVCRT tokenizes argumen. Berikut ini kutipannya:

  • Argumen dibatasi oleh spasi, yang merupakan spasi atau tab.
  • String yang dikelilingi oleh tanda kutip ganda ditafsirkan sebagai argumen tunggal, terlepas dari ruang putih yang terkandung di dalamnya. String yang dikutip dapat disematkan dalam argumen. Perhatikan bahwa tanda sisipan (^) tidak dikenali sebagai karakter pelarian atau pembatas.
  • Tanda kutip ganda yang diawali dengan garis miring terbalik, \ ", ditafsirkan sebagai tanda kutip ganda literal (").
  • Garis miring terbalik diartikan secara harfiah, kecuali mereka segera mendahului tanda kutip ganda.
  • Jika jumlah garis miring terbalik diikuti oleh tanda kutip ganda, maka satu garis miring terbalik () ditempatkan dalam array argv untuk setiap pasangan garis miring terbalik (\), dan tanda kutip ganda (") ditafsirkan sebagai pembatas string.
  • Jika jumlah garis miring terbalik yang aneh diikuti oleh tanda kutip ganda, maka satu garis miring terbalik () ditempatkan dalam array argv untuk setiap pasangan garis miring terbalik (\) dan tanda kutip ganda ditafsirkan sebagai urutan keluar oleh garis miring terbalik yang tersisa, menyebabkan tanda kutip ganda literal (") untuk ditempatkan di argv.

Microsoft "bahasa kumpulan" ( .bat) tidak terkecuali untuk lingkungan anarkis ini, dan telah mengembangkan aturan uniknya sendiri untuk tokenization dan melarikan diri. Itu juga terlihat seperti command prompt cmd.exe melakukan beberapa preprocessing dari argumen baris perintah (kebanyakan untuk substitusi variabel dan melarikan diri) sebelum meneruskan argumen ke proses yang baru dieksekusi. Anda dapat membaca lebih lanjut tentang detail level rendah dari bahasa kumpulan dan cmd yang lolos dalam jawaban yang sangat baik oleh jeb dan dbenham di halaman ini.


Mari kita membangun utilitas baris perintah sederhana dalam C dan melihat apa yang dikatakannya tentang kasus pengujian Anda:

int main(int argc, char* argv[]) {
    int i;
    for (i = 0; i < argc; i++) {
        printf("argv[%d][%s]\n", i, argv[i]);
    }
    return 0;
}

(Catatan: argv [0] selalu merupakan nama yang dapat dieksekusi, dan dihilangkan untuk singkatnya. Diuji pada Windows XP SP3. Disusun dengan Visual Studio 2005.)

> test.exe "a ""b"" c"
argv[1][a "b" c]

> test.exe """a b c"""
argv[1]["a b c"]

> test.exe "a"" b c
argv[1][a" b c]

Dan beberapa tes saya sendiri:

> test.exe a "b" c
argv[1][a]
argv[2][b]
argv[3][c]

> test.exe a "b c" "d e
argv[1][a]
argv[2][b c]
argv[3][d e]

> test.exe a \"b\" c
argv[1][a]
argv[2]["b"]
argv[3][c]
Mike Clark
sumber
Terima kasih atas jawaban Anda. Saya jadi semakin bingung untuk melihat bahwa TinyPerl tidak akan menampilkan apa yang dihasilkan oleh program Anda, dan saya mengalami kesulitan untuk memahami bagaimana [a "b" c]bisa [a "b] [c]melakukan pasca-pemrosesan.
Benoit
Sekarang saya berpikir tentang hal ini, tokenization dari baris perintah ini mungkin dilakukan sepenuhnya oleh runtime C. Dapat dieksekusi dapat ditulis sedemikian rupa sehingga bahkan tidak menggunakan runtime C, dalam hal ini saya pikir itu harus berurusan dengan baris perintah kata demi kata, dan bertanggung jawab untuk melakukan tokenization sendiri (jika ingin.) Atau bahkan jika aplikasi Anda tidak menggunakan runtime C, Anda dapat memilih untuk mengabaikan argc dan argv dan dapatkan baris perintah mentah melalui mis GetCommandLine. Win32 . Mungkin TinyPerl mengabaikan argv dan hanya tokenizing baris perintah mentah dengan aturannya sendiri.
Mike Clark
4
"Ingat bahwa dari sudut pandang Win32, baris perintah hanyalah string yang disalin ke ruang alamat dari proses baru. Bagaimana proses peluncuran dan proses baru menafsirkan string ini diatur bukan oleh aturan tetapi dengan konvensi." Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark
2
Terima kasih atas jawaban yang sangat bagus itu. Itu menjelaskan banyak pendapat saya. Dan itu juga menjelaskan mengapa saya kadang-kadang menemukan itu benar-benar jelek untuk bekerja dengan Windows ...
Benoit
Saya menemukan ini mengenai garis miring terbalik dan kutipan selama transformasi dari commandline ke argv, untuk program Win32 C ++. Hitungan backslash hanya dibagi dua ketika backslash terakhir diikuti oleh dblquote, dan dblquote mengakhiri sebuah string ketika ada sejumlah backslash sebelumnya.
Benoit
47

Aturan Ekspansi Persen

Berikut adalah penjelasan Fase 1 yang diperluas dalam jawaban jeb (Berlaku untuk mode bets dan mode baris perintah).

Fase 1) Ekspansi Persen Mulai dari kiri, pindai setiap karakter untuk %atau <LF>. Jika ditemukan maka

  • 1.05 (memotong garis pada <LF>)
    • Jika karakternya <LF>kemudian
      • Letakkan (abaikan) sisa garis dari <LF>depan
      • Goto Phase 1.5 (Strip <CR>)
    • Selain karakter %, maka lanjutkan ke 1.1
  • 1.1 (melarikan diri %) dilewati jika mode baris perintah
    • Jika mode batch dan diikuti oleh yang lain %maka
      Ganti %%dengan satu %dan lanjutkan pemindaian
  • 1.2 (memperluas argumen) dilewati jika mode baris perintah
    • Lain jika mode batch kemudian
      • Jika diikuti oleh *dan ekstensi perintah diaktifkan maka
        Ganti %*dengan teks dari semua argumen baris perintah (Ganti dengan apa pun jika tidak ada argumen) dan lanjutkan memindai.
      • Jika tidak diikuti <digit>kemudian
        Ganti %<digit>dengan nilai argumen (ganti dengan nol jika tidak ditentukan) dan lanjutkan pemindaian.
      • Jika tidak diikuti oleh ~dan ekstensi perintah diaktifkan maka
        • Jika diikuti oleh daftar pengubah argumen opsional yang valid diikuti dengan yang diharuskan <digit>maka
          Ganti %~[modifiers]<digit>dengan nilai argumen yang dimodifikasi (ganti dengan nol apa pun jika tidak didefinisikan atau jika ditentukan $ PATH: pengubah tidak ditentukan) dan lanjutkan pemindaian.
          Catatan: pengubah tidak peka huruf besar kecil dan dapat muncul beberapa kali dalam urutan apa pun, kecuali $ PATH: pengubah hanya dapat muncul sekali dan harus menjadi pengubah terakhir sebelum<digit>
        • Lain sintaks argumen yang dimodifikasi tidak valid menimbulkan kesalahan fatal: Semua perintah diuraikan dibatalkan, dan pemrosesan batch dibatalkan jika dalam mode batch!
  • 1.3 (memperluas variabel)
    • Lain jika ekstensi perintah dinonaktifkan maka
      Lihat string karakter berikutnya, melanggar sebelum %atau akhir buffer, dan menyebutnya VAR (mungkin daftar kosong)
      • Jika karakter berikutnya adalah %kemudian
        • Jika VAR didefinisikan maka
          Ganti %VAR%dengan nilai VAR dan lanjutkan pemindaian
        • Jika tidak ada mode batch maka
          Hapus %VAR%dan lanjutkan pemindaian
        • Lain goto 1.4
      • Lain goto 1.4
    • Lain jika ekstensi perintah diaktifkan kemudian
      Lihat rangkaian karakter berikutnya, pecahkan sebelum % :atau akhir buffer, dan panggil mereka VAR (mungkin daftar kosong). Jika VAR rusak sebelum :dan karakter %selanjutnya dimasukkan :sebagai karakter terakhir dalam VAR dan istirahat sebelumnya %.
      • Jika karakter berikutnya adalah %kemudian
        • Jika VAR didefinisikan maka
          Ganti %VAR%dengan nilai VAR dan lanjutkan pemindaian
        • Jika tidak ada mode batch maka
          Hapus %VAR%dan lanjutkan pemindaian
        • Lain goto 1.4
      • Lain jika karakter berikutnya adalah :kemudian
        • Jika VAR tidak terdefinisi maka
          • Jika mode batch maka
            Hapus %VAR:dan lanjutkan pemindaian.
          • Lain goto 1.4
        • Lain jika karakter berikutnya adalah ~kemudian
          • Jika string karakter berikutnya cocok dengan pola, [integer][,[integer]]%lalu
            Ganti %VAR:~[integer][,[integer]]%dengan substring nilai VAR (mungkin menghasilkan string kosong) dan lanjutkan pemindaian.
          • Lain goto 1.4
        • Lain jika diikuti oleh =atau *=kemudian
          Pencarian variabel tidak valid dan ganti sintaks menimbulkan kesalahan fatal: Semua perintah yang diuraikan dibatalkan, dan pemrosesan batch dibatalkan jika dalam mode batch!
        • Lain jika string karakter berikutnya cocok dengan pola [*]search=[replace]%, di mana pencarian dapat menyertakan set karakter kecuali =, dan ganti boleh menyertakan set karakter apa pun kecuali %, lalu
          Ganti %VAR:[*]search=[replace]%dengan nilai VAR setelah melakukan pencarian dan ganti (mungkin mengakibatkan string kosong) dan lanjutkan memindai
        • Lain goto 1.4
  • 1.4 (strip%)
    • Lain jika mode batch kemudian
      Hapus %dan lanjutkan pemindaian dimulai dengan karakter berikutnya setelah%
    • Lain lindungi yang terdepan %dan lanjutkan pemindaian yang dimulai dengan karakter berikutnya setelah yang diawetkan%

Di atas membantu menjelaskan mengapa kumpulan ini

@echo off
setlocal enableDelayedExpansion
set "1var=varA"
set "~f1var=varB"
call :test "arg1"
exit /b  
::
:test "arg1"
echo %%1var%% = %1var%
echo ^^^!1var^^^! = !1var!
echo --------
echo %%~f1var%% = %~f1var%
echo ^^^!~f1var^^^! = !~f1var!
exit /b

Memberikan hasil ini:

%1var% = "arg1"var
!1var! = varA
--------
%~f1var% = P:\arg1var
!~f1var! = varB

Catatan 1 - Fase 1 terjadi sebelum pengakuan pernyataan REM. Ini sangat penting karena itu berarti bahkan sebuah komentar dapat menghasilkan kesalahan fatal jika memiliki sintaksis ekspansi argumen tidak valid atau pencarian variabel tidak valid dan ganti sintaks!

@echo off
rem %~x This generates a fatal argument expansion error
echo this line is never reached

Catatan 2 - Konsekuensi lain yang menarik dari aturan% parsing: Variabel yang mengandung: dalam nama dapat didefinisikan, tetapi mereka tidak dapat diperluas kecuali ekstensi perintah dinonaktifkan. Ada satu pengecualian - nama variabel yang mengandung titik dua tunggal pada bagian akhir dapat diperluas saat ekstensi perintah diaktifkan. Namun, Anda tidak dapat melakukan substring atau mencari dan mengganti operasi pada nama variabel yang diakhiri dengan tanda titik dua. File batch di bawah ini (milik jeb) menunjukkan perilaku ini

@echo off
setlocal
set var=content
set var:=Special
set var::=double colon
set var:~0,2=tricky
set var::~0,2=unfortunate
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%
echo Now with DisableExtensions
setlocal DisableExtensions
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%

Catatan 3 - Hasil menarik dari urutan aturan penguraian yang dijabarkan jeb di posnya: Saat melakukan pencarian dan ganti dengan ekspansi yang tertunda, karakter khusus dalam istilah pencarian dan penggantian harus diloloskan atau dikutip. Tetapi situasinya berbeda untuk ekspansi persen - istilah penemuan tidak boleh melarikan diri (meskipun dapat dikutip). String persen ganti mungkin atau mungkin tidak memerlukan pelarian atau penawaran, tergantung pada niat Anda.

@echo off
setlocal enableDelayedExpansion
set "var=this & that"
echo %var:&=and%
echo "%var:&=and%"
echo !var:^&=and!
echo "!var:&=and!"

Aturan Ekspansi Tertunda

Berikut ini penjelasan yang diperluas dan lebih akurat tentang fase 5 dalam jawaban jeb (Berlaku untuk mode batch dan mode baris perintah)

Fase 5) Ekspansi Tertunda

Fase ini dilewati jika salah satu dari kondisi berikut ini berlaku:

  • Ekspansi yang tertunda dinonaktifkan.
  • Perintah berada di dalam blok yang di-kurung di kedua sisi pipa.
  • Token perintah yang masuk adalah skrip batch "telanjang", yang berarti itu tidak terkait dengan CALL, tanda kurung blok, segala bentuk gabungan perintah ( &, &&atau ||), atau pipa |.

Proses ekspansi yang tertunda diterapkan ke token secara independen. Perintah mungkin memiliki beberapa token:

  • Token perintah. Untuk sebagian besar perintah, nama perintah itu sendiri adalah token. Tetapi beberapa perintah memiliki wilayah khusus yang dianggap sebagai TOKEN untuk fase 5.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN, Di mana perbandingan adalah salah satu dari ==, equ, neq, lss, leq, gtr, ataugeq
  • Argumen token
  • Token tujuan pengalihan (satu per pengalihan)

Tidak ada perubahan yang dilakukan pada token yang tidak mengandung !.

Untuk setiap token yang mengandung setidaknya satu !, pindai setiap karakter dari kiri ke kanan untuk ^atau !, dan jika ditemukan, maka

  • 5.1 (caret escape) Diperlukan untuk !atau ^literal
    • Jika karakter adalah tanda sisir ^maka
      • Hapus ^
      • Pindai karakter berikutnya dan pertahankan sebagai karakter literal
      • Lanjutkan pemindaian
  • 5.2 (memperluas variabel)
    • Jika karakter !, maka
      • Jika ekstensi perintah dinonaktifkan maka
        Lihat rangkaian karakter berikutnya, pecahkan sebelum !atau <LF>, dan panggil mereka VAR (mungkin daftar kosong)
        • Jika karakter berikutnya adalah !kemudian
          • Jika VAR didefinisikan, maka
            Ganti !VAR!dengan nilai VAR dan lanjutkan pemindaian
          • Lain jika mode batch maka
            Hapus!VAR! dan lanjutkan pemindaian
          • Lain goto 5.2.1
        • Lain goto 5.2.1
      • Lain jika ekstensi perintah diaktifkan maka
        Lihatlah tali berikutnya karakter, melanggar sebelum !, :atau <LF>, dan memanggil VAR mereka (mungkin daftar kosong). Jika VAR rusak sebelum :dan karakter !selanjutnya dimasukkan :sebagai karakter terakhir dalam VAR dan istirahat sebelumnya!
        • Jika karakter berikutnya adalah !kemudian
          • Jika VAR ada, maka
            Ganti !VAR!dengan nilai VAR dan lanjutkan pemindaian
          • Jika tidak ada mode batch maka
            Hapus !VAR!dan lanjutkan pemindaian
          • Lain goto 5.2.1
        • Lain jika karakter berikutnya adalah :kemudian
          • Jika VAR tidak terdefinisi maka
            • Jika mode batch maka
              Hapus !VAR:dan lanjutkan pemindaian
            • Lain goto 5.2.1
          • Jika karakter berikutnya adalah ~kemudian
            • Jika string karakter berikutnya cocok dengan pola, [integer][,[integer]]!lalu Ganti !VAR:~[integer][,[integer]]!dengan substring nilai VAR (mungkin menghasilkan string kosong) dan lanjutkan pemindaian.
            • Lain goto 5.2.1
          • Lain jika string karakter berikutnya cocok dengan pola [*]search=[replace]!, di mana pencarian dapat menyertakan set karakter kecuali =, dan ganti boleh menyertakan set karakter apa pun kecuali !, lalu
            Ganti !VAR:[*]search=[replace]!dengan nilai VAR setelah melakukan pencarian dan ganti (mungkin menghasilkan string kosong) dan lanjutkan pemindaian
          • Lain goto 5.2.1
        • Lain goto 5.2.1
      • 5.2.1
        • Jika mode batch kemudian lepaskan !
          Else terkemuka simpan yang memimpin!
        • Lanjutkan pemindaian dimulai dengan karakter berikutnya setelah memimpin diawetkan !
dbenham
sumber
3
+1, Hanya sintaks dan aturan titik dua yang hilang di sini untuk %definedVar:a=b%vs %undefinedVar:a=b%dan %var:~0x17,-010%formulir
jeb
2
Poin bagus - Saya memperluas bagian ekspansi variabel untuk mengatasi masalah Anda. Saya juga memperluas bagian ekspansi argumen untuk mengisi beberapa detail yang hilang.
dbenham
2
Setelah mendapatkan beberapa umpan balik pribadi tambahan dari jeb, saya menambahkan aturan untuk nama variabel yang diakhiri dengan titik dua, dan menambahkan catatan 2. Saya juga menambahkan catatan 3 hanya karena saya pikir itu menarik dan penting.
dbenham
1
@aschipfl - Ya, saya mempertimbangkan untuk membahas lebih detail tentang itu, tetapi tidak ingin turun ke lubang kelinci itu. Saya sengaja tidak berkomitmen ketika saya menggunakan istilah [integer] .Ada lebih banyak info di Rules tentang bagaimana CMD.EXE mem-parsing angka .
dbenham
1
Saya kehilangan aturan ekspansi untuk konteks cmd, seperti itu tidak ada karakter yang dipesan untuk karakter pertama dari nama variabel seperti %<digit>, %*atau %~. Dan perubahan perilaku untuk variabel yang tidak ditentukan. Mungkin Anda perlu membuka jawaban kedua
jeb
7

Seperti yang ditunjukkan, perintah dilewatkan seluruh string argumen di μSoft land, dan terserah mereka untuk menguraikannya menjadi argumen terpisah untuk digunakan sendiri. Tidak ada konsistensi dalam hal ini antara berbagai program, dan oleh karena itu tidak ada satu set aturan untuk menggambarkan proses ini. Anda benar-benar perlu memeriksa setiap kasing sudut untuk pustaka C apa pun yang digunakan program Anda.

Sejauh .batfile sistem berjalan, di sini adalah tes itu:

c> type args.cmd
@echo off
echo cmdcmdline:[%cmdcmdline%]
echo 0:[%0]
echo *:[%*]
set allargs=%*
if not defined allargs goto :eof
setlocal
@rem Wot about a nice for loop?
@rem Then we are in the land of delayedexpansion, !n!, call, etc.
@rem Plays havoc with args like %t%, a"b etc. ugh!
set n=1
:loop
    echo %n%:[%1]
    set /a n+=1
    shift
    set param=%1
    if defined param goto :loop
endlocal

Sekarang kita dapat menjalankan beberapa tes. Lihat apakah Anda dapat mengetahui apa yang coba dilakukan μSoft:

C>args a b c
cmdcmdline:[cmd.exe ]
0:[args]
*:[a b c]
1:[a]
2:[b]
3:[c]

Sejauh ini baik-baik saja. (Aku akan meninggalkan yang tidak menarik %cmdcmdline%dan %0mulai sekarang.)

C>args *.*
*:[*.*]
1:[*.*]

Tidak ada ekspansi nama file.

C>args "a b" c
*:["a b" c]
1:["a b"]
2:[c]

Tidak ada pengupasan kutipan, meskipun kutipan mencegah pemisahan argumen.

c>args ""a b" c
*:[""a b" c]
1:[""a]
2:[b" c]

Kutipan ganda berturut-turut menyebabkan mereka kehilangan kemampuan parsing khusus yang mungkin mereka miliki. Contoh @ Beniot:

C>args "a """ b "" c"""
*:["a """ b "" c"""]
1:["a """]
2:[b]
3:[""]
4:[c"""]

Kuis: Bagaimana Anda meneruskan nilai var lingkungan apa pun sebagai argumen tunggal (yaitu, sebagai %1) ke file bat?

c>set t=a "b c
c>set t
t=a "b c
c>args %t%
1:[a]
2:["b c]
c>args "%t%"
1:["a "b]
2:[c"]
c>Aaaaaargh!

Sane parsing sepertinya rusak selamanya.

Untuk hiburan Anda, coba tambahkan aneka ^, \, ', &(& c.) Karakter untuk contoh-contoh ini.

bobbogo
sumber
Untuk melewatkan% t% sebagai argumen tunggal, Anda dapat menggunakan "% t:" = \ "%" Artinya, gunakan% VAR: str = replacement% sintaks untuk ekspansi variabel. Karakter meta shell seperti | dan & dalam konten variabel masih dapat diekspos dan mengacaukan cangkang, kecuali jika Anda melarikan diri lagi ....
Toughy
@ Sulit Jadi, dalam contoh saya, tadalah a "b c. Apakah Anda memiliki resep untuk mendapatkan orang 6 karakter ( a, 2 × ruang, ", b, dan c) untuk tampil sebagai %1di dalam .cmd? Saya suka pemikiran Anda. args "%t:"=""%"cukup dekat :-)
bobbogo
5

Anda sudah memiliki beberapa jawaban hebat di atas, tetapi untuk menjawab satu bagian dari pertanyaan Anda:

set a =b, echo %a %b% c% → bb c%

Apa yang terjadi di sana adalah bahwa karena Anda memiliki spasi sebelum =, variabel dibuat dipanggil %a<space>% ketika Anda echo %a %yang dievaluasi dengan benar b.

Bagian yang tersisa b% c%kemudian dievaluasi sebagai teks biasa + variabel yang tidak terdefinisi % c%, yang harus digaungkan sebagai diketik, untuk saya echo %a %b% c%kembalibb% c%

Saya menduga bahwa kemampuan untuk memasukkan spasi dalam nama variabel lebih merupakan pengawasan daripada 'fitur' yang direncanakan

SS64
sumber
0

sunting: lihat jawaban yang diterima, apa yang salah di sini dan hanya menjelaskan cara meneruskan baris perintah ke TinyPerl.


Mengenai kutipan, saya merasa bahwa perilakunya adalah sebagai berikut:

  • Ketika sebuah " ditemukan, string globbing dimulai
  • saat string menggumpal terjadi:
    • setiap karakter yang bukan a " merupakan globbed
    • Ketika sebuah " ditemukan:
      • jika diikuti oleh ""(dengan demikian rangkap tiga ") maka kutipan ganda ditambahkan ke string
      • jika diikuti oleh "(dengan demikian dobel ") maka kuotasi ganda ditambahkan ke string dan string globbing berakhir
      • jika karakter berikutnya tidak ", string globbing berakhir
    • ketika garis berakhir, string menggumpal berakhir.

Pendeknya:

"a """ b "" c"""terdiri dari dua string: a " b "danc"

"a"", "a"""dan "a""""semuanya adalah string yang sama jika pada akhir baris

Benoit
sumber
tokenizer dan string globbing tergantung pada perintah! Sebuah "set" berfungsi berbeda dari "panggilan" atau bahkan "jika"
jeb
ya, tapi bagaimana dengan perintah eksternal? Saya kira cmd.exe selalu memberikan argumen yang sama kepada mereka?
Benoit
1
cmd.exe selalu melewati hasil ekspansi sebagai string bukan token ke perintah eksternal. Itu tergantung pada perintah eksternal bagaimana cara melarikan diri dan tokenize itu, findstr menggunakan backslash yang berikutnya dapat menggunakan sesuatu yang lain
jeb
0

Perhatikan bahwa Microsoft telah menerbitkan kode sumber Terminalnya. Ini dapat bekerja mirip dengan baris perintah sehubungan dengan parsing sintaksis. Mungkin seseorang tertarik untuk menguji aturan parsing rekayasa balik sesuai dengan aturan parsing terminal.

Tautan ke kode sumber.

pengguna7427029
sumber