Dalam urutan apa shell menjalankan perintah dan mengalirkan redirection?

32

Saya mencoba mengalihkan keduanya stdoutdan stderrke file hari ini, dan saya menemukan ini:

<command> > file.txt 2>&1

Ini tampaknya dialihkan stderrke stdoutpertama, dan kemudian hasilnya stdoutdialihkan ke file.txt.

Namun, mengapa tidak memesan <command> 2>&1 > file.txt? Orang tentu akan membaca ini sebagai (dengan asumsi eksekusi dari kiri ke kanan) perintah yang dieksekusi pertama, stderryang diarahkan ke stdoutdan kemudian, hasilnya stdoutditulis untuk file.txt. Tetapi di atas hanya mengalihkan stderrke layar.

Bagaimana shell menginterpretasikan kedua perintah?

Latih Heartnet
sumber
7
TLCL mengatakan ini "Pertama kita mengarahkan output standar ke file, dan kemudian kita mengarahkan file deskriptor 2 (standard error) ke file deskriptor satu (output standar)" dan "Perhatikan bahwa urutan pengalihan signifikan. Pengalihan kesalahan standar harus selalu terjadi setelah mengarahkan keluaran standar atau tidak berfungsi "
Zanna
@Zanna: Ya, pertanyaan saya justru berasal dari membaca di TLCL! :) Saya tertarik mengetahui mengapa itu tidak berhasil, yaitu, bagaimana shell mengartikan perintah secara umum.
Latih Heartnet
Nah, dalam pertanyaan Anda Anda mengatakan sebaliknya "Ini tampaknya mengarahkan 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 stdout berikutnya tidak akan menyertakan stderr (yaitu pengalihan stderr ke layar selesai sebelum pengalihan stdout terjadi?)
Zanna
7
Saya tahu ini adalah hal kuno untuk dikatakan, tetapi cangkang Anda dilengkapi dengan manual yang menjelaskan hal-hal ini - misalnya pengalihan dalam bashmanual . Omong-omong, pengalihan bukan perintah.
Reinier Post
Perintah tidak dapat dieksekusi sebelum pengalihan diatur: Ketika execv-sistem panggilan keluarga dipanggil untuk benar-benar menyerahkan subproses ke perintah yang dimulai, shell kemudian keluar dari loop (tidak ada lagi kode yang dieksekusi dalam proses itu) ) dan tidak memiliki cara yang mungkin untuk mengendalikan apa yang terjadi sejak saat itu; Dengan demikian, semua pengalihan harus dilakukan sebelum eksekusi dimulai, sementara shell memiliki salinan dirinya yang sedang berjalan dalam proses fork()untuk menjalankan perintah Anda.
Charles Duffy

Jawaban:

41

Ketika Anda menjalankan <command> 2>&1 > file.txtstderr diarahkan 2>&1ke ke mana stdout saat ini pergi, terminal Anda. Setelah itu, stdout diarahkan ke file oleh >, tetapi stderr tidak diarahkan dengan itu, jadi tetap sebagai output terminal.

Dengan <command> > file.txt 2>&1stdout pertama-tama diarahkan ke file oleh >, kemudian 2>&1mengarahkan stderr ke tempat stdout pergi, yang merupakan file.

Mungkin tampaknya kontra intuitif untuk memulai, tetapi ketika Anda memikirkan pengalihan dengan cara ini, dan ingat bahwa mereka diproses dari kiri ke kanan, itu jauh lebih masuk akal.

Arronikal
sumber
Masuk akal jika Anda berpikir dalam hal file-deskriptor dan panggilan "dup / fdreopen" dijalankan secara berurutan dari kiri ke kanan
Mark K Cowan
20

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 dengan nomor deskriptor file mereka di sini: mereka adalah file deskriptor 0, 1 dan 2 masing-masing.

Sekarang, di set pengalihan pertama, kami memiliki > file.txtdan 2>&1.

Begitu:

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

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2sekarang 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, urutan dibalik:

  1. 2>&1: fd/2sekarang pergi ke mana pun fd/1saat ini, yang berarti tidak ada yang berubah:

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

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

Poin penting adalah bahwa pengalihan tidak berarti bahwa deskriptor file yang dialihkan akan mengikuti semua perubahan di masa depan untuk deskriptor file target; itu hanya akan mengambil kondisi saat ini .

muru
sumber
Terima kasih, ini sepertinya penjelasan yang lebih alami! :) Anda membuat kesalahan ketik sedikit di 2.bagian kedua; fd/1 -> file.txtdan tidak fd/2 -> file.txt.
Latih Heartnet
11

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

Baris Perintah Linux oleh William Shotts mengatakan

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

ini masuk akal, tapi kemudian

Perhatikan bahwa urutan pengalihan signifikan. Pengalihan kesalahan standar harus selalu terjadi setelah mengarahkan output standar atau tidak berfungsi

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

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

Jadi, in command > file 2>&1, shell mengirim stdout ke file, kemudian mengirim stderr ke stdout (yang sedang dikirim ke file). Padahal, pada command 2>&1 > fileshell pertama-tama redirect stderr ke stdout (yaitu menampilkannya di terminal tempat stdout biasanya berjalan) dan kemudian setelah itu, mengarahkan ulang stdout ke file. TLCL menyesatkan dalam mengatakan bahwa kita harus mengarahkan ulang stdout pertama: karena kita dapat mengarahkan stderr ke file terlebih dahulu dan kemudian mengirim stdout ke sana. Apa yang tidak bisa kita lakukan adalah mengarahkan ulang stdout ke stderr atau sebaliknya sebelum diarahkan ke suatu 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 dengan stderr, tetapi tidak, itu mengarahkan ulang stdout ke stderr (layar) terlebih dahulu, dan kemudian hanya mengarahkan ulang stderr, seperti ketika kita mencobanya sebaliknya ...

Saya harap ini membawa sedikit cahaya ...

Zanna
sumber
Jauh lebih fasih!
Arronical
Ah, sekarang saya mengerti! Terima kasih banyak, @Zanna dan @Arronical! Baru memulai perjalanan baris perintah saya. :)
Train Heartnet
@ TrainHeartnet sangat menyenangkan! Semoga Anda menikmatinya sama seperti saya: D
Zanna
@Zanna: Memang, saya! : D
Train Heartnet
2
@ TrainHeartnet jangan khawatir, dunia frustrasi dan kegembiraan menanti Anda!
Arronical
10

Anda sudah mendapatkan beberapa jawaban yang sangat bagus. Biar saya tekankan bahwa ada dua konsep berbeda yang terlibat di sini, pemahamannya sangat membantu:

Latar Belakang: Deskriptor file vs. tabel file

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

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

Jadi, ketika Anda menggunakan >atau <di shell Anda, Anda cukup mengganti pointer dari masing-masing file descriptor untuk menunjuk ke hal lain. Sintaks 2>&1hanya menunjuk deskriptor 2 ke manapun 1 poin. > file.txtcukup terbuka file.txtuntuk menulis dan membiarkan STDOUT (file decsriptor 1) menunjukkan itu.

Ada barang lain, misalnya 2>(xxx) (yaitu: membuat proses baru berjalan xxx, membuat pipa, menghubungkan file descriptor 0 dari proses baru ke ujung pembacaan pipa dan menghubungkan file deskriptor 2 dari proses asli ke akhir penulisan pipa).

Ini juga merupakan dasar untuk "file handle magic" pada perangkat lunak selain shell Anda. Misalnya, Anda bisa, dalam skrip Perl Anda, dupmelisensikan deskriptor file STDOUT menjadi yang lain (sementara), lalu membuka kembali STDOUT ke file sementara yang baru dibuat. Mulai saat ini, semua output STDOUT dari skrip Perl Anda sendiri dan semua system()panggilan skrip itu akan berakhir di file sementara itu. Setelah selesai, Anda dapat dupmengirim kembali STDOUT Anda ke deskriptor sementara tempat Anda menyimpannya, dan presto, semuanya seperti sebelumnya. Anda bahkan dapat menulis ke deskriptor sementara itu, jadi sementara output STDOUT Anda yang sebenarnya pergi ke file sementara, Anda masih dapat benar-benar menampilkan barang-barang ke STDOUT nyata (umumnya, pengguna).

Menjawab

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

Dalam urutan apa shell menjalankan perintah dan mengalirkan redirection?

Kiri ke kanan.

<command> > file.txt 2>&1

  1. fork dari proses baru.
  2. Buka file.txtdan simpan penunjuknya di file descriptor 1 (STDOUT).
  3. Arahkan STDERR (file descriptor 2) ke apa pun yang ditunjukkan 1 fd sekarang (yang lagi-lagi sudah dibuka file.txttentu saja).
  4. exec itu <command>

Ini rupanya mengarahkan stderr ke stdout terlebih dahulu, dan kemudian stdout yang dihasilkan diarahkan ke file.txt.

Ini masuk akal jika hanya ada satu meja, tetapi seperti yang dijelaskan di atas ada dua. File deskriptor tidak menunjuk satu sama lain secara rekursif, tidak masuk akal untuk berpikir "redirect STDERR ke STDOUT". Pikiran yang benar adalah "arahkan STDERR ke mana pun STDOUT menunjuk". Jika Anda mengubah STDOUT nanti, STDERR tetap di tempatnya, itu tidak secara ajaib sejalan dengan perubahan lebih lanjut ke STDOUT.

AnoE
sumber
Menambah bit untuk "file handle magic" - walaupun tidak langsung menjawab pertanyaan, saya belajar sesuatu yang baru hari ini ...
Floris
3

Perintahnya dari kiri ke kanan. Manual Bash telah membahas apa yang Anda minta. Kutipan dari REDIRECTIONbagian 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 pengalihan diselesaikan terlebih dahulu sebelum perintah dijalankan! Lihat https://askubuntu.com/a/728386/295286

Sergiy Kolodyazhnyy
sumber
3

Selalu dari kiri ke kanan ... kecuali kapan

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

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

  1. Ekspansi penjepit "{}"
  2. Ekspansi Tilde "~"
  3. Parameter shell dan ekspresi variabel "$"
  4. Substitusi perintah "$ (command)"
  5. Ekspresi aritmatika "$ ((EKSPRESI))"
  6. Proses Substitusi apa yang kita bicarakan di sini "<(LIST)" atau "> (LIST)"
  7. Pemisahan Kata "'<spasi> <tab> <baris baru>'"
  8. Ekspansi Nama File "*", "?", Dll.

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

WinEunuuchs2Unix
sumber
1
Agar lebih jelas: proses penggantian dan pengalihan adalah dua operasi independen. Anda dapat melakukan satu tanpa yang lain. Memiliki proses substitusi tidak berarti bahwa bash memutuskan untuk mengubah urutan proses pengalihan
muru