Pertanyaan Dalam urutan apa shell mengeksekusi perintah dan melakukan streaming redirection?


Saya mencoba mengalihkan keduanya stdout dan stderr ke file hari ini, dan saya menemukan ini:

<command> > file.txt 2>&1

Ini tampaknya mengarahkan ulang stderr untuk stdout pertama, dan kemudian hasilnya stdout dialihkan ke file.txt.

Namun, mengapa tidak pesanan <command> 2>&1 > file.txt? Satu akan alami membaca ini sebagai (dengan asumsi eksekusi dari kiri ke kanan) perintah yang dieksekusi pertama, stderr sedang dialihkan ke stdout dan kemudian, hasilnya stdout sedang ditulis untuk file.txt. Tetapi hanya pengalihan di atas stderr ke layar.

Bagaimana shell menafsirkan kedua perintah?


31
2017-12-13 08:20


asal


TLCL mengatakan ini "Pertama kita redirect output standar ke file, dan kemudian kita redirect file descriptor 2 (standard error) ke file deskriptor satu (output standar)" dan "Perhatikan bahwa urutan redirections signifikan. Pengalihan dari standard error harus selalu terjadi setelah mengarahkan output standar atau tidak berfungsi " - Zanna
@Zanna: Ya, pertanyaan saya justru datang dari membaca itu di TLCL! :) Saya tertarik untuk mengetahui mengapa itu tidak akan berhasil, yaitu bagaimana shell menafsirkan perintah secara umum. - Train Heartnet
Nah, dalam pertanyaan Anda, Anda mengatakan sebaliknya "Ini tampaknya mengarahkan ulang stderr ke stdout pertama ..." - apa yang saya mengerti dari TLCL, adalah bahwa shell mengirim stdout ke file dan kemudian stderr ke stdout (yaitu ke file). Interpretasi saya adalah bahwa jika Anda mengirim stderr ke stdout, maka itu akan ditampilkan di terminal, dan pengalihan berikutnya stdout tidak akan menyertakan stderr (yaitu pengalihan stderr ke layar selesai sebelum redirection stdout terjadi?) - Zanna
Saya tahu ini adalah hal lama untuk dikatakan, tetapi shell Anda dilengkapi dengan manual yang menjelaskan hal-hal ini - mis. pengalihan dalam bash manual. By the way, pengalihan bukan perintah. - Reinier Post
Perintah tidak bisa dijalankan sebelum pengalihan diatur: Saat execv- syscall keluarga dipanggil untuk benar-benar menyerahkan subproses ke perintah yang dimulai, shell kemudian keluar dari loop (tidak lagi memiliki kode mengeksekusi dalam proses itu) dan tidak memiliki cara yang mungkin untuk mengendalikan apa yang terjadi dari titik itu; pengarahan ulang demikian semuanya harus dilakukan sebelum eksekusi dimulai, sementara shell memiliki salinan dirinya sendiri yang berjalan dalam proses itu fork()ed untuk menjalankan perintah Anda. - Charles Duffy


Jawaban:


Saat Anda berlari <command> 2>&1 > file.txt stderr dialihkan oleh 2>&1 ke tempat stdout saat ini pergi, terminal Anda. Setelah itu, stdout dialihkan ke file oleh >, tetapi stderr tidak diarahkan dengan itu, jadi tetap sebagai output terminal.

Dengan <command> > file.txt 2>&1 stdout pertama kali dialihkan ke file oleh >, kemudian 2>&1 redirect stderr ke tempat stdout pergi, yang merupakan file.

Ini mungkin tampak kontra intuitif untuk memulai dengan, tetapi ketika Anda memikirkan pengarahan ulang dengan cara ini, dan ingat bahwa mereka diproses dari kiri ke kanan lebih masuk akal.


41
2017-12-13 09:43



Masuk akal jika Anda berpikir dalam hal pendeskripsi file dan panggilan "dup / fdreopen" dijalankan dari kiri ke kanan - Mark K Cowan


Mungkin masuk akal jika Anda melacaknya.

Pada awalnya, stderr dan stdout pergi ke hal yang sama (biasanya terminal, yang saya sebut di sini pts):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

Saya mengacu pada stdin, stdout dan stderr oleh mereka pendeskripsi file angka di sini: mereka adalah file deskriptor 0, 1 dan 2 masing-masing.

Sekarang, di set pengalihan pertama, kami punya > file.txt dan 2>&1.

Begitu:

  1. > file.txt: fd/1 sekarang pergi ke file.txt. Dengan >, 1 adalah deskripsi file tersirat ketika tidak ada yang ditentukan, jadi ini 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    
  2. 2>&1: fd/2 sekarang pergi ke mana pun fd/1  saat ini pergi:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt
    

Di sisi lain, dengan 2>&1 > file.txt, pesanan dibalik:

  1. 2>&1: fd/2 sekarang pergi ke mana pun fd/1 sedang berjalan, yang artinya tidak ada perubahan:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
    
  2. > file.txt: fd/1 sekarang pergi ke file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    

Poin pentingnya adalah redirection tidak berarti bahwa file deskriptor yang diarahkan akan mengikuti semua perubahan yang akan terjadi pada deskriptor file target; itu hanya akan memakan waktu arus negara.


20
2017-12-13 14:25



Terima kasih, ini sepertinya penjelasan yang lebih alami! :) Anda membuat sedikit kesalahan ketik 2. dari bagian kedua; fd/1 -> file.txt dan tidak fd/2 -> file.txt. - Train Heartnet
@TrainHeartnet oh, koreksi, terima kasih! - muru


Saya pikir itu membantu untuk berpikir bahwa shell akan mengatur redirection di sebelah kiri pertama, dan lengkap sebelum mengatur pengalihan berikutnya.

Baris Perintah Linux oleh William Shotts mengatakan

Pertama kita mengarahkan output standar ke file, dan kemudian kita redirect   file descriptor 2 (standard error) ke file descriptor satu (standar   keluaran)

ini masuk akal, tapi kemudian

Perhatikan bahwa urutan pengalihan sangat signifikan. Itu   pengalihan kesalahan standar harus selalu terjadi setelah pengalihan   keluaran standar atau tidak berfungsi

tetapi sebenarnya, kita dapat mengalihkan stdout ke stderr setelah mengalihkan stderr ke file dengan efek yang sama

$ uname -r 2>/dev/null 1>&2
$ 

Jadi, dalam command > file 2>&1, shell mengirim stdout ke file, kemudian mengirim stderr ke stdout (yang sedang dikirim ke file). Padahal, di command 2>&1 > fileshell pertama redirects stderr ke stdout (yaitu menampilkannya di terminal di mana stdout biasanya berjalan) dan kemudian setelah itu, arahan ulang stdout ke file. TLCL menyesatkan dengan mengatakan bahwa kita harus mengalihkan stdout pertama: karena kita dapat mengalihkan stderr ke file terlebih dahulu dan kemudian mengirim stdout ke file tersebut. Apa yang tidak bisa kita lakukan, adalah redirect stdout ke stderr atau sebaliknya sebelum pengalihan ke file. Contoh lain

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

Kita mungkin berpikir ini akan membuang stdout ke tempat yang sama seperti stderr, tetapi tidak, itu mengarahkan ulang stdout ke stderr (layar) terlebih dahulu, dan kemudian hanya mengarahkan ulang stderr, seperti ketika kita mencoba sebaliknya ...

Saya harap ini membawa sedikit cahaya ...


11
2017-12-13 09:43



Jauh lebih fasih! - Arronical
Ah, sekarang aku mengerti! Terima kasih banyak, @Zanna dan @Arronical! Baru saja memulai perjalanan baris perintah saya. :) - Train Heartnet
@TrainHeartnet, ini menyenangkan! Semoga Anda menikmatinya sama seperti saya: D - Zanna
@Zanna: Sungguh, saya! : D - Train Heartnet
@TrainHeartnet jangan khawatir, dunia frustrasi dan sukacita menanti Anda! - Arronical


Anda sudah mendapat beberapa jawaban yang sangat bagus. Biarkan saya menekankan bahwa ada dua konsep yang berbeda yang terlibat di sini, yang pemahamannya sangat membantu:

Latar belakang: File descriptor vs tabel file

Deskriptor file Anda hanyalah angka 0 ... n, yang merupakan indeks dalam tabel deskriptor file dalam proses Anda. Dengan konvensi, STDIN = 0, STDOUT = 1, STDERR = 2 (perhatikan bahwa ketentuannya STDIN dll. di sini hanya simbol / makro yang digunakan oleh konvensi dalam beberapa bahasa pemrograman dan halaman manual, tidak ada "objek" yang sebenarnya yang disebut STDIN; untuk tujuan diskusi ini, STDIN aku s 0, dll.).

Tabel deskriptor file itu sendiri tidak berisi informasi apa pun tentang apa sebenarnya file itu. Sebaliknya, ini berisi pointer ke tabel file yang berbeda; yang terakhir berisi informasi tentang file fisik yang sebenarnya (atau perangkat blok, atau pipa, atau apa pun yang dapat alamat Linux melalui mekanisme file) dan informasi lebih lanjut (yaitu, apakah itu untuk membaca atau menulis).

Jadi saat Anda menggunakan > atau < di shell Anda, Anda cukup mengganti pointer dari deskriptor file masing-masing untuk menunjuk ke sesuatu yang lain. Sintaksnya 2>&1 cukup arahkan deskripsi 2 ke dimanapun 1 poin. > file.txt hanya terbuka file.txt untuk menulis dan memungkinkan STDOUT (file decsriptor 1) menunjukkan itu.

Ada barang lainnya, mis. 2>(xxx)  (yaitu: buat proses baru berjalan xxx, buat pipa, hubungkan file descriptor 0 dari proses baru ke ujung pembacaan pipa dan hubungkan file descriptor 2 dari proses asli ke ujung penulisan pipa).

Ini juga merupakan dasar untuk "file menangani sihir" di perangkat lunak lain daripada shell Anda. Misalnya, Anda bisa, dalam skrip Perl Anda, dupmengesahkan deskriptor file STDOUT ke yang lain (sementara), kemudian buka kembali STDOUT ke file sementara yang baru dibuat. Dari titik ini pada, semua STDOUT output dari skrip Perl Anda sendiri dan semua system() panggilan skrip itu akan berakhir di file sementara itu. Setelah selesai, Anda dapat kembalidup STDOUT Anda ke deskriptor sementara yang Anda simpan, dan presto, semuanya sama seperti sebelumnya. Anda bahkan dapat menulis ke deskriptor sementara itu sementara itu, sementara output STDOUT Anda yang sebenarnya pergi ke file sementara, Anda masih dapat benar-benar mengeluarkan barang ke nyata STDOUT (umumnya, pengguna).

Menjawab

Untuk menerapkan informasi latar belakang yang diberikan di atas untuk pertanyaan Anda:

Dalam urutan apa shell mengeksekusi perintah dan melakukan streaming redirection?

Kiri ke kanan.

<command> > file.txt 2>&1

  1. fork dari proses baru.
  2. Buka file.txt dan simpan penunjuknya dalam pendeskripsi file 1 (STDOUT).
  3. Arahkan STDERR (file descriptor 2) ke poin fd 1 yang sekarang (yang lagi-lagi sudah dibuka) file.txt tentu saja).
  4. exec itu <command>

Ini tampaknya mengarahkan ulang stderr ke stdout pertama, dan kemudian stdout yang dihasilkan dialihkan ke file.txt.

Ini masuk akal jika hanya ada satu meja, tetapi seperti yang dijelaskan di atas ada dua. Deskriptor file tidak menunjuk satu sama lain secara rekursif, tidak masuk akal untuk berpikir "redirect STDERR ke STDOUT". Pikiran yang benar adalah "arahkan STDERR ke titik STDOUT mana pun". Jika Anda mengubah STDOUT kemudian, STDERR tetap di tempat itu, itu tidak secara ajaib mengikuti perubahan lebih lanjut ke STDOUT.


10
2017-12-13 22:34



Upvoting untuk bit "file handle magic" - meskipun tidak secara langsung menjawab pertanyaan saya belajar sesuatu yang baru hari ini ... - Floris


Urutannya dari kiri ke kanan. Panduan Bash sudah mencakup apa yang Anda minta. Kutipan dari REDIRECTION bagian dari manual:

   Redirections  are  processed  in  the
   order they appear, from left to right.

dan beberapa baris kemudian:

   Note that the order of redirections is signifi‐
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli‐
   cated from the standard output before the stan‐
   dard output was redirected to dirlist.

Penting untuk dicatat bahwa redirection diselesaikan terlebih dahulu sebelum perintah apa pun dijalankan! Lihat https://askubuntu.com/a/728386/295286 


3
2017-12-13 22:47





Selalu kiri ke kanan ... kecuali kapan

Sama seperti di Matematika kita lakukan dari kiri ke kanan kecuali perkalian dan pembagian dilakukan sebelum penambahan dan pengurangan, kecuali operasi di dalam kurung (+ -) akan dilakukan sebelum perkalian dan pembagian.

Sesuai panduan pemula Bash di sini (Panduan Pemula Bash) ada 8 urutan hierarki dari apa yang datang lebih dulu (sebelum kiri ke kanan):

  1. Perluasan brace "{}"
  2. Ekspansi tilde "~"
  3. Parameter shell dan ekspresi variabel "$"
  4. Substitusi perintah "$ (perintah)"
  5. Ekspresi aritmatika "$ ((EKSPRESI))"
  6. Substitusi Proses apa yang sedang kita bicarakan di sini "<(LIST)" atau "> (DAFTAR)"
  7. Word Splitting "'<space> <tab> <newline>'"
  8. Perluasan Nama File "*", "?", Dll.

Jadi selalu kiri ke kanan ... kecuali ketika ...


3
2017-12-17 03:18



Untuk menjadi jelas: proses substitusi dan redirection adalah dua operasi independen. Anda dapat melakukannya tanpa yang lainnya. Memiliki substitusi proses tidak berarti bash memutuskan untuk mengubah urutan proses pengalihan - muru