Dalam kode “{exec> / dev / null; }> / dev / null ”apa yang terjadi di bawah tenda?

15

Ketika Anda mengarahkan ulang daftar perintah yang berisi pengalihan exec, exec> / dev / null tampaknya tidak akan diterapkan setelahnya, seperti dengan:

{ exec >/dev/null; } >/dev/null; echo "Hi"

"Hai" dicetak.

Saya mendapat kesan bahwa {}daftar perintah tidak dianggap sebagai subkulit kecuali itu adalah bagian dari pipa, jadi exec >/dev/nullseharusnya masih diterapkan dalam lingkungan shell saat ini dalam pikiran saya.

Sekarang jika Anda mengubahnya ke:

{ exec >/dev/null; } 2>/dev/null; echo "Hi"

tidak ada output seperti yang diharapkan; deskriptor file 1 tetap menunjuk / dev / null untuk perintah selanjutnya juga. Ini ditunjukkan oleh rerunning:

{ exec >/dev/null; } >/dev/null; echo "Hi"

yang tidak akan memberikan hasil.

Saya mencoba membuat skrip dan menggantinya, tetapi saya masih tidak yakin persis apa yang terjadi di sini.

Pada setiap titik dalam skrip ini, apa yang terjadi pada deskriptor file STDOUT?

EDIT: Menambahkan output strace saya:

read(255, "#!/usr/bin/env bash\n{ exec 1>/de"..., 65) = 65
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
close(10)                               = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
fstat(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
ioctl(1, TCGETS, 0x7ffee027ef90)        = -1 ENOTTY (Inappropriate ioctl for device)
write(1, "hi\n", 3)                     = 3
Joey Pabalinas
sumber
Itu aneh; Saya tidak dapat mereproduksi close(10). Bisakah Anda juga memposting seluruh konten skrip tempat Anda menjalankan strace?
DepressedDaniel
@DepressedDaniel Inilah skrip dan strace lengkapnya: skrip strace
Joey Pabalinas
Anda memiliki nyasar ;setelah }, yang mengubah arti > /dev/nulluntuk tidak berlaku untuk daftar majemuk {}setelah semua.
DepressedDaniel
@DepressedDaniel Ah, Anda sepenuhnya benar! Sekarang hasilnya adalah apa yang saya harapkan; terima kasih atas jawaban anda!
Joey Pabalinas

Jawaban:

17

Ayo ikuti

{ exec >/dev/null; } >/dev/null; echo "Hi"

selangkah demi selangkah.

  1. Ada dua perintah:

    Sebuah. { exec >/dev/null; } >/dev/null, diikuti oleh

    b. echo "Hi"

    Shell pertama-tama mengeksekusi perintah (a) dan kemudian perintah (b).

  2. Pengerjaan { exec >/dev/null; } >/dev/nullhasil sebagai berikut:

    Sebuah. Pertama, shell melakukan pengalihan >/dev/null dan ingat untuk membatalkannya ketika perintah berakhir .

    b. Kemudian, shell dieksekusi { exec >/dev/null; }.

    c. Akhirnya, shell mengganti output standar kembali ke posisi semula. (Ini adalah mekanisme yang sama seperti in ls -lR /usr/share/fonts >~/FontList.txt-redirections dibuat hanya selama durasi perintah.)

  3. Setelah perintah pertama selesai, shell mengeksekusi echo "Hi". Output standar adalah dimanapun itu sebelum perintah pertama.

AlexP
sumber
Apakah ada alasan mengapa 2a dieksekusi sebelum 2b? (kanan-ke-kiri)
Joey Pabalinas
5
Pengalihan harus dilakukan sebelum perintah yang mereka terapkan, bukan? Bagaimana mereka bisa bekerja sebaliknya?
AlexP
Aha, tidak pernah terpikir seperti itu! Dua yang pertama adalah jawaban yang bagus; berikan sedikit sebelum saya memutuskan satu, tapi saya menghargai kedua penjelasan!
Joey Pabalinas
Sayangnya saya hanya dapat memilih satu jawaban, jadi saya memilih jawaban ini karena ini sedikit kurang teknis dan dengan demikian saya pikir itu akan dapat membantu bahkan pengguna yang kurang paham teknologi. Namun @DepressedDaniel memiliki jawaban yang sama hebatnya di sini yang menawarkan penjelasan yang lebih mendalam.
Joey Pabalinas
14

Agar tidak menggunakan sub-shell atau sub-proses, ketika output daftar senyawa {}disalurkan >, shell menyimpan deskriptor STDOUT sebelum menjalankan daftar senyawa dan mengembalikannya setelah. Jadi, exec >dalam daftar majemuk tidak membawa efeknya melewati titik di mana deskriptor lama dipulihkan sebagai STDOUT.

Mari kita lihat bagian yang relevan dari strace bash -c '{ exec >/dev/null; } >/dev/null; echo hi' 2>&1 | cat -n:

   132  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   133  fcntl(1, F_GETFD)                       = 0
   134  fcntl(1, F_DUPFD, 10)                   = 10
   135  fcntl(1, F_GETFD)                       = 0
   136  fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
   137  dup2(3, 1)                              = 1
   138  close(3)                                = 0
   139  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   140  fcntl(1, F_GETFD)                       = 0
   141  fcntl(1, F_DUPFD, 10)                   = 11
   142  fcntl(1, F_GETFD)                       = 0
   143  fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
   144  dup2(3, 1)                              = 1
   145  close(3)                                = 0
   146  close(11)                               = 0
   147  dup2(10, 1)                             = 1
   148  fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
   149  close(10)                               = 0

Anda dapat melihat bagaimana, pada baris 134, deskriptor 1( STDOUT) disalin ke deskriptor lain dengan indeks setidaknya 10(itulah yang F_DUPFDdilakukannya; ia mengembalikan deskriptor terendah yang tersedia mulai dari angka yang diberikan setelah menggandakan ke deskriptor itu). Juga lihat bagaimana, pada baris 137, hasil open("/dev/null")(deskriptor 3) disalin ke deskriptor 1( STDOUT). Akhirnya, on line 147, yang lama STDOUTdisimpan pada deskriptor 10disalin kembali ke deskriptor 1( STDOUT). Efek bersih adalah untuk mengisolasi perubahan ke STDOUTonline 144(yang sesuai dengan bagian dalam exec >/dev/null).

DepresiDaniel
sumber
Karena FD 1 ditimpa oleh FD 3 pada baris 137, mengapa baris 141 tidak menunjuk 10 ke / dev / null?
Joey Pabalinas
@ JoeyPabalinas Line 141 menduplikasi FD 1 (yaitu, stdout) ke deskriptor berikutnya yang tersedia setelah 10 , yang ternyata menjadi 11, seperti yang dapat Anda lihat dalam nilai balik dari pemanggilan sistem itu. 10 hanya dikodekan ke dalam bash sehingga penyimpanan deskriptor bash tidak akan mengganggu deskriptor satu digit yang dapat Anda manipulasi dalam skrip Anda exec.
DepressedDaniel
Jadi fcntl (1, F_DUPFD, 10) akan selalu merujuk ke STDOUT di mana pun FD 1 menunjuk?
Joey Pabalinas
@ JoeyPabalinas Tidak yakin apa pertanyaan Anda. FD 1 IS STDOUT. Mereka adalah hal yang sama.
DepressedDaniel
Menambahkan output strace penuh ke posting asli saya.
Joey Pabalinas
8

Perbedaan antara { exec >/dev/null; } >/dev/null; echo "Hi" dan { exec >/dev/null; }; echo "Hi"adalah bahwa pengalihan ganda dilakukan dup2(10, 1);sebelum menutup fd 10 yang merupakan salinan asli stdout, sebelum menjalankan perintah berikutnya ( echo).

Itu terjadi seperti itu karena pengalihan luar sebenarnya overlay pengalihan batin. Itu sebabnya ia menyalin kembali stdoutfd asli setelah selesai.

Julie Pelletier
sumber
+1 untuk menjelaskan perbedaan dengan cara mudah. Jawaban AlexP tidak memiliki penjelasan ini.
Kamil Maciorowski