Memahami Substitusi Perintah Baca-File Bash

11

Saya mencoba memahami bagaimana tepatnya Bash memperlakukan baris berikut:

$(< "$FILE")

Menurut halaman manual Bash, ini setara dengan:

$(cat "$FILE")

dan saya bisa mengikuti garis penalaran untuk baris kedua ini. Bash melakukan ekspansi variabel pada $FILE, memasuki substitusi perintah, meneruskan nilai $FILEke cat, cat output isi $FILEke output standar, substitusi perintah selesai dengan mengganti seluruh baris dengan output standar yang dihasilkan dari perintah di dalam, dan Bash mencoba untuk mengeksekusi seperti perintah sederhana.

Namun, untuk baris pertama yang saya sebutkan di atas, saya memahaminya sebagai: Bash melakukan substitusi variabel aktif $FILE, Bash membuka $FILEuntuk membaca input standar, entah bagaimana input standar disalin ke output standar , substitusi perintah selesai, dan Bash mencoba untuk mengeksekusi standar yang dihasilkan keluaran.

Bisakah seseorang tolong jelaskan kepada saya bagaimana isi $FILEdari stdin ke stdout?

Stanley Yu
sumber

Jawaban:

-3

Ini <bukan secara langsung aspek substitusi perintah bash . Ini adalah operator pengalihan (seperti pipa), yang memungkinkan beberapa shell tanpa perintah (POSIX tidak menentukan perilaku ini).

Mungkin akan lebih jelas dengan lebih banyak ruang:

echo $( < $FILE )

ini efektif * sama dengan lebih aman-POSIX

echo $( cat $FILE )

... yang juga efektif *

echo $( cat < $FILE )

Mari kita mulai dengan versi terakhir itu. Ini berjalan cattanpa argumen, yang berarti akan membaca dari input standar. $FILEdialihkan ke input standar karena <, sehingga catmenempatkan isinya dimasukkan ke dalam output standar. The $(command)substitusi kemudian mendorong catproduksi 's menjadi argumen untuk echo.

Dalam bash(tetapi tidak dalam standar POSIX), Anda dapat menggunakan <tanpa perintah. bash(dan zshdan kshtetapi tidak dash) akan menafsirkan bahwa seolah-olah cat <, meskipun tanpa meminta subproses baru. Karena ini asli dari shell, ia lebih cepat daripada menjalankan perintah eksternal cat. * Inilah sebabnya saya katakan "efektif sama dengan."

Adam Katz
sumber
Jadi dalam paragraf terakhir ketika Anda mengatakan " bashakan menafsirkan itu sebagai cat filename", apakah maksud Anda perilaku ini khusus untuk memerintahkan penggantian? Karena jika saya berlari < filenamedengan sendirinya, bash tidak mengatasinya. Ini tidak akan menghasilkan apa-apa dan mengembalikan saya ke prompt.
Stanley Yu
Perintah masih dibutuhkan. @cuonglm diubah teks asli saya dari cat < filenameke cat filenamemana saya menentang dan dapat kembali.
Adam Katz
1
Pipa adalah jenis file. Operator shell |membuat pipa antara dua subproses (atau, dengan beberapa shell, dari subproses ke input standar shell). Operator shell $(…)membuat pipa dari subproses ke shell itu sendiri (bukan ke input standar). Operator shell <tidak melibatkan pipa, hanya membuka file dan memindahkan deskriptor file ke input standar.
Gilles 'SO- stop being evil'
3
< filetidak sama dengan cat < file(kecuali di zshmana rasanya $READNULLCMD < file). < filesempurna POSIX dan hanya terbuka fileuntuk membaca dan kemudian tidak melakukan apa-apa (begitu filedekat langsung). Itu $(< file)atau `< file`itu adalah operator khusus ksh, zshdan bash(dan perilaku dibiarkan tidak ditentukan dalam POSIX). Lihat jawaban saya untuk detailnya.
Stéphane Chazelas
2
Untuk menempatkan komentar @ StéphaneChazelas di cahaya lain: ke perkiraan pertama, $(cmd1) $(cmd2)biasanya akan sama dengan $(cmd1; cmd2). Tapi melihat kasus di mana cmd2adalah < file. Jika kita katakan $(cmd1; < file), file tersebut tidak dibaca, tetapi, dengan $(cmd1) $(< file), itu. Jadi tidak benar untuk mengatakan bahwa $(< file)itu hanya kasus biasa $(command)dengan perintah < file.   $(< …)adalah kasus khusus substitusi perintah, dan bukan penggunaan pengalihan biasa.
Scott
14

$(<file)(juga bekerja dengan `<file`) adalah operator khusus dari shell Korn yang disalin oleh zshdan bash. Memang terlihat sangat mirip dengan substitusi perintah tetapi sebenarnya tidak.

Dalam shell POSIX, perintah sederhana adalah:

< file var1=value1 > file2 cmd 2> file3 args 3> file4

Semua bagian adalah opsional, Anda dapat memiliki pengalihan saja, hanya perintah, hanya tugas atau kombinasi.

Jika ada pengalihan tetapi tidak ada perintah, pengalihan dilakukan (jadi a > fileakan terbuka dan memotong file), tetapi kemudian tidak ada yang terjadi. Begitu

< file

Terbuka fileuntuk membaca, tetapi tidak ada yang terjadi karena tidak ada perintah. Jadi filekemudian ditutup dan hanya itu. Jika $(< file)itu adalah substitusi perintah yang sederhana , maka itu tidak akan berkembang.

Dalam spesifikasi POSIX , dalam $(script), jika scripthanya terdiri dari pengalihan, yang menghasilkan hasil yang tidak ditentukan . Itu untuk memungkinkan perilaku khusus cangkang Korn.

Dalam ksh (di sini diuji dengan ksh93u+), jika skrip terdiri dari satu dan hanya satu perintah sederhana (meskipun komentar diperbolehkan sebelum dan sesudah) yang hanya terdiri dari pengalihan (tidak ada perintah, tidak ada tugas) dan jika pengalihan pertama adalah stdin (fd 0) hanya input ( <, <<atau <<<) redirection, jadi:

  • $(< file)
  • $(0< file)
  • $(<&3)(juga $(0>&3)sebenarnya karena itu berlaku operator yang sama)
  • $(< file > foo 2> $(whatever))

tapi tidak:

  • $(> foo < file)
  • maupun $(0<> file)
  • maupun $(< file; sleep 1)
  • maupun $(< file; < file2)

kemudian

  • semua kecuali pengalihan pertama diabaikan (mereka diurai)
  • dan itu berkembang ke konten file / heredoc / herestring (atau apa pun yang dapat dibaca dari deskriptor file jika menggunakan hal-hal seperti <&3) dikurangi karakter garis belakang yang tertinggal.

seakan menggunakan $(cat < file)kecuali itu

  • bacaan dilakukan secara internal oleh shell dan bukan oleh cat
  • tidak ada pipa atau proses tambahan yang terlibat
  • sebagai konsekuensi dari hal di atas, karena kode di dalamnya tidak dijalankan dalam subkulit, modifikasi apa pun tetap setelahnya (seperti dalam $(<${file=foo.txt})atau $(<file$((++n))))
  • kesalahan baca (meskipun bukan kesalahan saat membuka file atau menggandakan deskriptor file) diabaikan secara diam-diam.

Dalam zsh, itu sama kecuali bahwa bahwa perilaku khusus yang hanya dipicu ketika hanya ada satu input redirection berkas ( <fileatau 0< file, tidak ada <&3, <<<here, < a < b...)

Namun, kecuali saat meniru shell lain, di:

< file
<&3
<<< here...

yaitu ketika hanya ada pengalihan input tanpa perintah, di luar substitusi perintah, zshmenjalankan $READNULLCMD(pager secara default), dan ketika ada pengalihan input dan output, $NULLCMD( catsecara default), bahkan jika $(<&3)tidak diakui sebagai yang khusus operator, itu masih akan bekerja seperti di ksholah dengan memanggil pager untuk melakukannya (pager itu bertindak seperti catstdout akan menjadi pipa).

Namun sementara kshitu $(< a < b)akan diperluas ke konten a, dalam zsh, itu berkembang ke konten adan b(atau hanya bjika multiosopsi dinonaktifkan), $(< a > b)akan menyalin ake bdan memperluas ke apa-apa, dll.

bash memiliki operator yang sama tetapi dengan beberapa perbedaan:

  • komentar diperbolehkan sebelum tetapi tidak setelah:

    echo "$(
       # getting the content of file
       < file)"

    bekerja tetapi:

    echo "$(< file
       # getting the content of file
    )"

    mengembang tanpa apa-apa.

  • seperti di zsh, hanya satu pengalihan stdin file, meskipun tidak ada jatuh kembali ke $READNULLCMD, jadi $(<&3), $(< a < b)lakukan pengalihan tetapi memperluas ke apa-apa.

  • untuk beberapa alasan, sementara bashtidak meminta cat, itu masih memalsukan proses yang memberi makan konten file melalui pipa membuatnya jauh lebih sedikit dari optimasi daripada di shell lain. Ini berlaku seperti di $(cat < file)mana catakan menjadi builtin cat.
  • sebagai konsekuensi dari hal di atas, setiap perubahan yang dilakukan di dalam hilang setelah itu (dalam $(<${file=foo.txt}), yang disebutkan di atas misalnya, bahwa $filepenugasan hilang setelah itu).

In bash, IFS= read -rd '' var < file (juga berfungsi zsh) adalah cara yang lebih efektif untuk membaca konten file teks menjadi variabel. Ini juga memiliki manfaat melestarikan karakter baris baru. Lihat juga $mapfile[file]di zsh(dalam zsh/mapfilemodul dan hanya untuk file biasa) yang juga berfungsi dengan file biner.

Perhatikan bahwa varian berbasis pdksh kshmemiliki beberapa variasi dibandingkan dengan ksh93. Yang menarik, di mksh(salah satu dari shell yang diturunkan pdksh), di

var=$(<<'EOF'
That's multi-line
test with *all* sorts of "special"
characters
EOF
)

dioptimalkan dalam hal isi dokumen di sini (tanpa karakter trailing) diperluas tanpa file atau pipa sementara yang digunakan seperti halnya kasus untuk dokumen di sini, yang menjadikannya sintaks kutipan multi-baris yang efektif.

Menjadi portabel untuk semua versi ksh, zshdan bash, yang terbaik adalah membatasi hanya dengan $(<file)menghindari komentar dan mengingat bahwa modifikasi pada variabel yang dibuat di dalamnya mungkin atau tidak dapat dipertahankan.

Stéphane Chazelas
sumber
Apakah benar itu $(<)adalah operator pada nama file? Apakah <dalam $(<)operator redirection, atau bukan operator sendiri, dan harus menjadi bagian dari seluruh operator $(<)?
Tim
@Tim, tidak masalah bagaimana Anda ingin memanggil mereka. $(<file)dimaksudkan untuk memperluas ke konten filedengan cara yang sama seperti $(cat < file)akan. Cara melakukannya bervariasi dari shell ke shell yang dijelaskan panjang lebar dalam jawabannya. Jika Anda suka, Anda dapat mengatakan bahwa itu adalah operator khusus yang dipicu ketika apa yang tampak seperti substitusi perintah (secara sintaksis) berisi apa yang tampak seperti pengalihan stdin tunggal (secara sintaksis), tetapi sekali lagi dengan peringatan dan variasi tergantung pada shell seperti yang tercantum di sini .
Stéphane Chazelas
@ StéphaneChazelas: Menarik, seperti biasa; Saya telah menandai ini. Jadi, n<&mdan n>&mlakukan hal yang sama? Saya tidak tahu itu, tapi saya kira itu tidak terlalu mengejutkan.
Scott
@Scott, ya, mereka berdua melakukan dup(m, n). Saya bisa melihat beberapa bukti ksh86 menggunakan stdio dan beberapa fdopen(fd, "r" or "w"), jadi mungkin ada artinya. Tetapi menggunakan stdio di shell tidak masuk akal, jadi saya tidak berharap Anda akan menemukan shell modern di mana itu akan membuat perbedaan. Satu perbedaan adalah itu >&nadalah dup(n, 1)(kependekan dari 1>&n), sedangkan <&nadalah dup(n, 0)(kependekan dari 0<&n).
Stéphane Chazelas
Baik. Kecuali, tentu saja, bentuk dua argumen dari panggilan duplikasi deskriptor file disebut dup2(); dup()hanya membutuhkan satu argumen dan, seperti open(), menggunakan deskriptor file terendah yang tersedia. (Hari ini saya belajar bahwa ada dup3()fungsi .)
Scott
8

Karena bashmelakukannya secara internal untuk Anda, perluas nama file dan berikan file ke output standar, seperti jika Anda melakukannya $(cat < filename). Ini adalah fitur bash, mungkin Anda perlu melihat bashkode sumber untuk mengetahui cara kerjanya.

Di sini fungsi untuk menangani fitur ini (Dari bashkode sumber, file builtins/evalstring.c):

/* Handle a $( < file ) command substitution.  This expands the filename,
   returning errors as appropriate, then just cats the file to the standard
   output. */
static int
cat_file (r)
     REDIRECT *r;
{
  char *fn;
  int fd, rval;

  if (r->instruction != r_input_direction)
    return -1;

  /* Get the filename. */
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing++;
  fn = redirection_expand (r->redirectee.filename);
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing--;

  if (fn == 0)
    {
      redirection_error (r, AMBIGUOUS_REDIRECT);
      return -1;
    }

  fd = open(fn, O_RDONLY);
  if (fd < 0)
    {
      file_error (fn);
      free (fn);
      return -1;
    }

  rval = zcatfd (fd, 1, fn);

  free (fn);
  close (fd);

  return (rval);
}

Catatan yang $(<filename)tidak persis setara dengan $(cat filename); yang terakhir akan gagal jika nama file dimulai dengan tanda hubung -.

$(<filename)berasal dari ksh, dan ditambahkan ke bashdari Bash-2.02.

cuonglm
sumber
1
cat filenameakan gagal jika nama file dimulai dengan tanda hubung karena cat menerima opsi. Anda dapat mengatasinya pada kebanyakan sistem modern dengan cat -- filename.
Adam Katz
-1

Pikirkan substitusi perintah sebagai menjalankan perintah seperti biasa dan membuang output pada titik di mana Anda menjalankan perintah.

Output dari perintah dapat digunakan sebagai argumen ke perintah lain, untuk mengatur variabel, dan bahkan untuk menghasilkan daftar argumen dalam for for.

foo=$(echo "bar")akan menetapkan nilai variabel $fooke bar; output dari perintah echo bar.

Substitusi Perintah

iyrin
sumber
1
Saya percaya bahwa cukup jelas dari pertanyaan bahwa OP memahami dasar-dasar penggantian komando; pertanyaannya adalah tentang kasus khusus $(< file), dan dia tidak perlu tutorial tentang kasus umum. Jika Anda mengatakan itu $(< file)hanya kasus biasa $(command)dengan perintah < file, maka Anda mengatakan hal yang sama yang dikatakan Adam Katz , dan Anda berdua salah.
Scott