Theo thứ tự nào shell thực hiện các lệnh và chuyển hướng luồng?


32

Tôi đã cố gắng chuyển hướng cả hai stdoutstderrđến một tập tin ngày hôm nay, và tôi đã gặp điều này:

<command> > file.txt 2>&1

Điều này rõ ràng chuyển hướng stderrđến stdoutđầu tiên, và sau đó kết quả stdoutđược chuyển hướng đến file.txt.

Tuy nhiên, tại sao không phải là thứ tự <command> 2>&1 > file.txt? Một cách tự nhiên sẽ đọc điều này như (giả sử thực hiện từ trái sang phải) lệnh được thực thi trước, lệnh stderrđược chuyển hướng đến stdoutvà sau đó, kết quả stdoutđược ghi vào file.txt. Nhưng ở trên chỉ chuyển hướng stderrđến màn hình.

Làm thế nào để shell giải thích cả hai lệnh?


7
TLCL nói điều này "Đầu tiên chúng tôi chuyển hướng đầu ra tiêu chuẩn sang tệp và sau đó chúng tôi chuyển hướng mô tả tệp 2 (lỗi tiêu chuẩn) sang mô tả tệp một (đầu ra tiêu chuẩn)" và "Lưu ý rằng thứ tự của các chuyển hướng là đáng kể. phải luôn xảy ra sau khi chuyển hướng đầu ra tiêu chuẩn hoặc nó không hoạt động "
Zanna

@Zanna: Vâng, câu hỏi của tôi chính xác đến từ việc đọc nó trong TLCL! :) Tôi muốn biết lý do tại sao nó sẽ không hoạt động, tức là, cách shell diễn giải các lệnh nói chung.
Train Heartnet

Chà, trong câu hỏi của bạn, bạn nói ngược lại "Điều này rõ ràng chuyển hướng stderr thành stdout trước ..." - điều tôi hiểu từ TLCL, là shell gửi stdout vào tệp và sau đó stderr thành stdout (tức là vào tệp). Giải thích của tôi là nếu bạn gửi stderr tới thiết bị xuất chuẩn, thì nó sẽ được hiển thị trong thiết bị đầu cuối và việc chuyển hướng thiết bị xuất chuẩn sau đó sẽ không bao gồm thiết bị xuất chuẩn (tức là chuyển hướng của thiết bị xuất chuẩn đến màn hình hoàn thành trước khi chuyển hướng của thiết bị xuất chuẩn?)
Zanna

7
Tôi biết đây là một điều lỗi thời, nhưng vỏ của bạn đi kèm với một hướng dẫn giải thích những điều này - ví dụ như chuyển hướng trong bashhướng dẫn . Nhân tiện, chuyển hướng không phải là lệnh.
Rebier Post

Lệnh không thể được thực thi trước khi các chuyển hướng được thiết lập: Khi tòa nhà execv-f Family được gọi để thực sự chuyển quá trình con cho lệnh đang được khởi động, trình vỏ sẽ ra khỏi vòng lặp (không còn mã thực thi trong quy trình đó ) và không có cách nào khả thi để kiểm soát những gì xảy ra từ thời điểm đó; Do đó, tất cả các chuyển hướng phải được thực hiện trước khi bắt đầu thực thi, trong khi hệ vỏ có một bản sao của chính nó đang chạy trong quá trình nó fork()chạy để chạy lệnh của bạn.
Charles Duffy

Câu trả lời:


41

Khi bạn chạy <command> 2>&1 > file.txtstderr được chuyển hướng 2>&1đến nơi thiết bị xuất chuẩn hiện tại, thiết bị đầu cuối của bạn. Sau đó, thiết bị xuất chuẩn được chuyển hướng đến tệp bằng cách> , nhưng không được chuyển hướng với nó, vì vậy vẫn là đầu ra cuối.

Với <command> > file.txt 2>&1thiết bị xuất chuẩn đầu tiên được chuyển hướng đến tệp trước >, sau đó2>&1 chuyển hướng thiết bị xuất chuẩn đến nơi thiết bị xuất chuẩn, đó là tệp.

Nó có vẻ phản tác dụng khi bắt đầu, nhưng khi bạn nghĩ về các chuyển hướng theo cách này, và hãy nhớ rằng chúng được xử lý từ trái sang phải, nó có ý nghĩa hơn nhiều.


Thật ý nghĩa nếu bạn nghĩ về các mô tả tệp và các lệnh gọi "dup / fdreopen" được thực hiện theo thứ tự từ trái sang phải
Mark K Cowan

20

Nó có thể có ý nghĩa nếu bạn theo dõi nó ra.

Ban đầu, stderr và stdout đi đến cùng một thứ (thường là thiết bị đầu cuối, mà tôi gọi ở đây pts):

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

Tôi đang đề cập đến stdin, stdout và stderr bởi các số mô tả tệp của chúng ở đây: chúng lần lượt là các mô tả tệp 0, 1 và 2.

Bây giờ, trong bộ chuyển hướng đầu tiên, chúng ta có > file.txt2>&1.

Vì thế:

  1. > file.txt: fd/1bây giờ đi đến file.txt. Với >, 1là mô tả tập tin ngụ ý khi không có gì được chỉ định, vì vậy đây là 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2bây giờ đi đến bất cứ nơi nào fd/1 hiện đang đi:

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

Mặt khác, với 2>&1 > file.txt, thứ tự được đảo ngược:

  1. 2>&1: fd/2bây giờ đi đến bất cứ nơi nào fd/1hiện đang đi, có nghĩa là không có gì thay đổi:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1bây giờ đi đến file.txt:

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

Điểm quan trọng là chuyển hướng không có nghĩa là bộ mô tả tệp được chuyển hướng sẽ tuân theo tất cả các thay đổi trong tương lai đối với bộ mô tả tệp đích; nó sẽ chỉ đưa vào trạng thái hiện tại .


Cảm ơn bạn, điều này có vẻ như một lời giải thích tự nhiên hơn! :) Bạn đã mắc một lỗi đánh máy nhẹ trong 2.phần thứ hai; fd/1 -> file.txtvà không fd/2 -> file.txt.
Train Heartnet

11

Tôi nghĩ rằng nó sẽ giúp nghĩ rằng shell sẽ thiết lập chuyển hướng bên trái trước và hoàn thành nó trước khi thiết lập chuyển hướng tiếp theo.

Dòng lệnh Linux của William Shotts nói

Trước tiên, chúng tôi chuyển hướng đầu ra tiêu chuẩn đến tệp, và sau đó chúng tôi chuyển hướng mô tả tệp 2 (lỗi tiêu chuẩn) sang mô tả tệp một (đầu ra tiêu chuẩn)

Điều này có ý nghĩa, nhưng sau đó

Lưu ý rằng thứ tự của các chuyển hướng là rất quan trọng. Việc chuyển hướng lỗi tiêu chuẩn phải luôn xảy ra sau khi chuyển hướng đầu ra tiêu chuẩn hoặc nó không hoạt động

nhưng thực tế, chúng ta có thể chuyển hướng stdout sang stderr sau khi chuyển hướng stderr sang một tệp có cùng tác dụng

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

Vì vậy, trong command > file 2>&1, shell gửi stdout đến một tệp, sau đó gửi stderr đến stdout (đang được gửi đến một tệp). Trong khi đó, trong command 2>&1 > fileshell đầu tiên chuyển hướng stderr sang stdout (tức là hiển thị nó trong terminal nơi stdout thường đi) và sau đó, chuyển hướng stdout vào tệp. TLCL gây hiểu lầm khi nói rằng chúng ta phải chuyển hướng thiết bị xuất chuẩn trước: vì chúng ta có thể chuyển hướng thiết bị xuất chuẩn sang tệp trước rồi gửi thiết bị xuất chuẩn đến nó. Những gì chúng ta không thể làm, là chuyển hướng stdout sang stderr hoặc ngược lại trước khi chuyển hướng đến một tệp. Một vi dụ khac

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

Chúng ta có thể nghĩ rằng điều này sẽ loại bỏ thiết bị xuất chuẩn đến cùng một vị trí với thiết bị xuất chuẩn, nhưng không, nó chuyển hướng thiết bị xuất chuẩn sang thiết bị xuất chuẩn trước, và sau đó chỉ chuyển hướng thiết bị xuất chuẩn, như khi chúng ta thử nó theo cách khác ...

Tôi hy vọng điều này mang lại một chút ánh sáng ...


Thật hùng hồn hơn nhiều!
Arronical

À, giờ thì tôi đã hiểu! Cảm ơn bạn rất nhiều, @Zanna và @Arronical! Chỉ cần bắt đầu trên hành trình dòng lệnh của tôi. :)
Train Heartnet

@TrainHeartnet thật là vui! Hy vọng bạn sẽ thích nó nhiều như tôi: D
Zanna

@Zanna: Thật vậy, tôi đây! : D
Train Heartnet

2
@TrainHeartnet không phải lo lắng, một thế giới của sự thất vọng và niềm vui đang chờ bạn!
Arronical

10

Bạn đã có một vài câu trả lời rất tốt rồi. Hãy để tôi nhấn mạnh mặc dù có hai khái niệm khác nhau liên quan ở đây, sự hiểu biết về nó giúp ích rất nhiều:

Bối cảnh: Mô tả tệp so với bảng tệp

Bộ mô tả tệp của bạn chỉ là số 0 ... n, là chỉ mục trong bảng mô tả tệp trong quy trình của bạn. Theo quy ước, STDIN = 0, STDOUT = 1, STDERR = 2 (lưu ý rằng các thuật ngữ, STDINv.v ... ở đây chỉ là các ký hiệu / macro được sử dụng bởi quy ước trong một số ngôn ngữ lập trình và trang man, không có một "đối tượng" thực tế nào được gọi là STDIN; mục đích của cuộc thảo luận này, STDIN 0, v.v.).

Bản thân bảng mô tả tệp đó không chứa bất kỳ thông tin nào về tệp thực sự là gì. Thay vào đó, nó chứa một con trỏ tới một bảng tệp khác; cái sau chứa thông tin về một tệp vật lý thực tế (hoặc thiết bị chặn hoặc đường ống hoặc bất kỳ thứ gì khác mà Linux có thể xử lý thông qua cơ chế tệp) và nhiều thông tin hơn (ví dụ, cho dù đó là để đọc hay viết).

Vì vậy, khi bạn sử dụng >hoặc <trong trình bao của mình, bạn chỉ cần thay thế con trỏ của bộ mô tả tệp tương ứng để trỏ đến một cái gì đó khác. Cú pháp 2>&1chỉ đơn giản là điểm mô tả 2 đến bất cứ nơi nào 1 điểm. > file.txtchỉ cần mở file.txtđể viết và để STDOUT (tệp decsriptor 1) trỏ đến đó.

Có một số tính năng khác, ví dụ: 2>(xxx) (ví dụ: tạo một quy trình mới đang chạy xxx, tạo một đường ống, kết nối bộ mô tả tệp 0 của quy trình mới với đầu đọc của ống và kết nối bộ mô tả tệp 2 của quy trình ban đầu với đầu ghi của ống).

Đây cũng là cơ sở cho "tệp xử lý ma thuật" trong phần mềm khác ngoài vỏ của bạn. Ví dụ: trong tập lệnh Perl của bạn, duphãy mô tả bộ mô tả tệp STDOUT thành một bộ mô tả (tạm thời) khác, sau đó mở lại STDOUT thành một tệp tạm thời mới được tạo. Từ thời điểm này, tất cả đầu ra STDOUT từ tập lệnh Perl của riêng bạn và tất cả các system()cuộc gọi của tập lệnh đó sẽ kết thúc trong tệp tạm thời đó. Khi hoàn tất, bạn có thể gửi lại dupSTDOUT của mình cho bộ mô tả tạm thời mà bạn đã lưu nó và trước đó, tất cả vẫn như trước. Bạn thậm chí có thể ghi vào bộ mô tả tạm thời trong khi đó, vì vậy trong khi đầu ra STDOUT thực tế của bạn chuyển đến tệp tạm thời, bạn vẫn có thể thực sự xuất nội dung sang STDOUT thực (thông thường, người dùng).

Câu trả lời

Để áp dụng thông tin cơ bản được đưa ra ở trên cho câu hỏi của bạn:

Theo thứ tự nào shell thực hiện các lệnh và chuyển hướng luồng?

Trái sang phải.

<command> > file.txt 2>&1

  1. fork tắt một quy trình mới.
  2. Mở file.txtvà lưu trữ con trỏ của nó trong bộ mô tả tệp 1 (STDOUT).
  3. Điểm STDERR (mô tả tệp 2) cho bất cứ điều gì fd 1 điểm đến ngay bây giờ (một lần nữa là file.txtkhóa học đã được mở ).
  4. exec các <command>

Điều này rõ ràng chuyển hướng thiết bị xuất chuẩn thành thiết bị xuất chuẩn trước, và sau đó thiết bị xuất chuẩn kết quả được chuyển hướng đến file.txt.

Điều này sẽ có ý nghĩa nếu chỉ có một bảng, nhưng như đã giải thích ở trên có hai bảng. Các bộ mô tả tệp không được trỏ vào nhau theo cách đệ quy, sẽ không có ý nghĩa gì khi nghĩ "chuyển hướng STDERR sang STDOUT". Suy nghĩ đúng là "điểm STDERR đến bất cứ nơi nào STDOUT điểm". Nếu bạn thay đổi STDOUT sau đó, STDERR sẽ giữ nguyên vị trí của nó, điều đó không kỳ diệu đi cùng với những thay đổi tiếp theo đối với STDOUT.


Nâng cấp cho bit "xử lý tập tin ma thuật" - mặc dù nó không trả lời trực tiếp câu hỏi tôi đã học được điều gì mới hôm nay ...
Floris

3

Thứ tự từ trái sang phải. Hướng dẫn của Bash đã bao gồm những gì bạn yêu cầu. Trích dẫn từ REDIRECTIONphần của hướng dẫn:

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

và một vài dòng sau:

   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.

Điều quan trọng cần lưu ý là chuyển hướng được giải quyết trước khi bất kỳ lệnh nào chạy! Xem https://askubfox.com/a/728386/295286


3

Nó luôn luôn từ trái sang phải ... ngoại trừ khi

Giống như trong Toán học, chúng ta thực hiện từ trái sang phải ngoại trừ phép nhân và phép chia được thực hiện trước khi cộng và trừ, ngoại trừ các phép toán trong ngoặc đơn (+ -) sẽ được thực hiện trước khi nhân và chia.

Theo hướng dẫn cho người mới bắt đầu Bash ở đây ( Hướng dẫn cho người mới bắt đầu Bash ), có 8 thứ tự phân cấp của cái đến trước (trước trái sang phải):

  1. Mở rộng cú đúp "{}"
  2. Mở rộng dấu ngã "~"
  3. Tham số Shell và biểu thức biến "$"
  4. Thay thế lệnh "$ (lệnh)"
  5. Biểu thức số học "$ ((EXPRESSION))"
  6. Xử lý thay thế những gì chúng ta đang nói ở đây "<(LIST)" hoặc "> (LIST)"
  7. Chia tách từ "'<dấu cách> <tab> <dòng mới>'"
  8. Mở rộng tên tệp "*", "?", V.v.

Vì vậy, nó luôn luôn từ trái sang phải ... ngoại trừ khi ...


1
Để rõ ràng: thay thế quá trình và chuyển hướng là hai hoạt động độc lập. Bạn có thể làm cái này mà không cần cái kia. Có sự thay thế quá trình không có nghĩa là bash quyết định thay đổi thứ tự mà nó xử lý chuyển hướng
muru
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.