Hiểu thay thế lệnh đọc-a-file của Bash


11

Tôi đang cố gắng hiểu chính xác cách Bash đối xử với dòng sau:

$(< "$FILE")

Theo trang người đàn ông Bash, điều này tương đương với:

$(cat "$FILE")

và tôi có thể theo dòng lý luận cho dòng thứ hai này. Thực hiện Bash mở rộng biến trên $FILE, vào thay thế lệnh, vượt qua giá trị của $FILEđể cat, con mèo đầu ra các nội dung của $FILEđầu ra tiêu chuẩn, kết thúc thay thế lệnh bằng cách thay thế toàn bộ phù hợp với đầu ra tiêu chuẩn kết quả từ bên trong lệnh, và những nỗ lực bash để thực hiện điều đó như một lệnh đơn giản.

Tuy nhiên, đối với dòng đầu tiên tôi đã đề cập ở trên, tôi hiểu nó như sau: Bash thực hiện thay thế biến $FILE, Bash mở $FILEđể đọc trên đầu vào tiêu chuẩn, bằng cách nào đó đầu vào tiêu chuẩn được sao chép sang đầu ra tiêu chuẩn , kết thúc thay thế lệnh và Bash cố gắng thực hiện tiêu chuẩn kết quả đầu ra.

Ai đó có thể vui lòng giải thích cho tôi làm thế nào nội dung $FILEđi từ stdin đến stdout?

Câu trả lời:


-3

Đây <không phải là một khía cạnh của sự thay thế lệnh bash . Nó là một toán tử chuyển hướng (như một đường ống), mà một số shell cho phép mà không có lệnh (POSIX không chỉ định hành vi này).

Có lẽ nó sẽ rõ ràng hơn với nhiều không gian hơn:

echo $( < $FILE )

điều này có hiệu quả * giống như POSIX an toàn hơn

echo $( cat $FILE )

... cũng hiệu quả *

echo $( cat < $FILE )

Hãy bắt đầu với phiên bản cuối cùng đó. Điều này chạy catkhông có đối số, có nghĩa là nó sẽ đọc từ đầu vào tiêu chuẩn. $FILEđược chuyển hướng vào đầu vào tiêu chuẩn do <, do đó, catđặt nội dung của nó được đưa vào đầu ra tiêu chuẩn. Các $(command)subsitution sau đó đẩy catra 's vào lập luận ủng hộ echo.

Trong bash(nhưng không phải trong tiêu chuẩn POSIX), bạn có thể sử dụng <mà không cần lệnh. bash(và zshkshkhông dash) sẽ diễn giải rằng như thể cat <, mặc dù không cần gọi một quy trình con mới. Vì đây là nguồn gốc của shell, nó nhanh hơn so với việc chạy lệnh bên ngoài cat. * Đây là lý do tại sao tôi nói "hiệu quả giống như."


Vì vậy, trong đoạn cuối cùng khi bạn nói " bashsẽ diễn giải điều đó như cat filename", bạn có nghĩa là hành vi này là cụ thể để thay thế lệnh? Bởi vì nếu tôi tự chạy < filename, bash sẽ không biến mất. Nó sẽ không xuất ra gì và đưa tôi trở lại dấu nhắc.
Stanley Yu

Một lệnh vẫn cần thiết. @cuonglm thay đổi văn bản ban đầu của tôi từ cat < filenameđể cat filenamemà tôi phản đối và có thể trở lại.
Adam Katz

1
Một đường ống là một loại tập tin. Toán tử shell |tạo một đường ống giữa hai quy trình con (hoặc, với một số shell, từ một quy trình con đến đầu vào tiêu chuẩn của shell). Toán tử shell $(…)tạo một đường ống từ một quy trình con đến chính shell (không phải đầu vào tiêu chuẩn của nó). Toán tử shell <không liên quan đến một đường ống, nó chỉ mở một tệp và di chuyển bộ mô tả tệp sang đầu vào tiêu chuẩn.
Gilles 'SO- ngừng trở thành ác quỷ'

3
< filekhông giống như cat < file(ngoại trừ ở zshnơi nó thích $READNULLCMD < file). < filelà POSIX hoàn hảo và chỉ mở fileđể đọc và sau đó không làm gì cả (vì vậy hãy fileđóng ngay lập tức). Đó là $(< file)hoặc `< file`đó là một toán tử đặc biệt của ksh, zshbash(và hành vi không được chỉ định trong POSIX). Xem câu trả lời của tôi để biết chi tiết.
Stéphane Chazelas

2
Để đưa nhận xét của @ StéphaneChazelas sang một khía cạnh khác: với xấp xỉ đầu tiên, $(cmd1) $(cmd2)thường sẽ giống như $(cmd1; cmd2). Nhưng nhìn vào trường hợp cmd2< file. Nếu chúng ta nói $(cmd1; < file), tập tin không được đọc, nhưng, với $(cmd1) $(< file), nó là. Vì vậy, không đúng khi nói rằng đó $(< file)chỉ là một trường hợp thông thường $(command)với lệnh < file.   $(< …)là một trường hợp đặc biệt của sự thay thế lệnh và không phải là cách sử dụng chuyển hướng thông thường.
Scott

13

$(<file)(cũng hoạt động với `<file`) là một toán tử đặc biệt của vỏ Korn được sao chép bởi zshbash. Nó trông rất giống như thay thế lệnh nhưng nó không thực sự.

Trong shell POSIX, một lệnh đơn giản là:

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

Tất cả các phần là tùy chọn, bạn chỉ có thể có chuyển hướng, chỉ lệnh, chỉ gán hoặc kết hợp.

Nếu có các chuyển hướng nhưng không có lệnh, các chuyển hướng được thực hiện (vì vậy a > filesẽ mở và cắt ngắn file), nhưng sau đó không có gì xảy ra. Vì thế

< file

Mở fileđể đọc, nhưng sau đó không có gì xảy ra vì không có lệnh. Vì vậy, filesau đó được đóng lại và đó là nó. Nếu $(< file)là một thay thế lệnh đơn giản , thì nó sẽ mở rộng thành không có gì.

Trong đặc tả POSIX , trong $(script), nếu scriptchỉ bao gồm các chuyển hướng, sẽ tạo ra kết quả không xác định . Đó là cho phép hành vi đặc biệt của vỏ Korn.

Trong ksh (ở đây đã được thử nghiệm ksh93u+), nếu tập lệnh bao gồm một và chỉ một lệnh đơn giản (mặc dù các bình luận được cho phép trước và sau) chỉ bao gồm các chuyển hướng (không có lệnh, không có chuyển nhượng) và nếu chuyển hướng đầu tiên là stdin (fd 0) chỉ chuyển hướng ( <, <<hoặc <<<) chuyển hướng, vì vậy:

  • $(< file)
  • $(0< file)
  • $(<&3)(cũng $(0>&3)thực sự là điều đó có hiệu lực cùng một nhà điều hành)
  • $(< file > foo 2> $(whatever))

nhưng không:

  • $(> foo < file)
  • cũng không $(0<> file)
  • cũng không $(< file; sleep 1)
  • cũng không $(< file; < file2)

sau đó

  • tất cả trừ chuyển hướng đầu tiên đều bị bỏ qua (chúng được phân tích cú pháp)
  • và nó mở rộng đến nội dung của tệp / heredoc / herestring (hoặc bất cứ điều gì có thể được đọc từ bộ mô tả tệp nếu sử dụng những thứ như <&3) trừ các ký tự dòng mới.

như thể sử dụng $(cat < file)ngoại trừ đó

  • việc đọc được thực hiện bên trong bởi vỏ chứ không phải bởi cat
  • không có đường ống cũng không có quá trình bổ sung
  • như một hệ quả của điều trên, vì mã bên trong không được chạy trong một lớp con, nên mọi sửa đổi vẫn còn sau đó (như trong $(<${file=foo.txt})hoặc $(<file$((++n))))
  • đọc lỗi (mặc dù không phải lỗi trong khi mở tệp hoặc sao chép mô tả tệp) bị bỏ qua trong âm thầm.

Trong zsh, nó giống ngoại trừ rằng hành vi đặc biệt chỉ được kích hoạt khi chỉ có một tập tin chuyển hướng đầu vào ( <filehoặc 0< file, không <&3, <<<here, < a < b...)

Tuy nhiên, ngoại trừ khi mô phỏng các vỏ khác, trong:

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

đó là khi chỉ có các chuyển hướng đầu vào mà không có lệnh, bên ngoài thay thế lệnh, zshchạy $READNULLCMD(máy nhắn tin theo mặc định) và khi có cả chuyển hướng đầu vào và đầu ra, $NULLCMD( cattheo mặc định), do đó ngay cả khi $(<&3)không được nhận dạng là đặc biệt Toán tử, nó vẫn sẽ hoạt động như kshmặc dù bằng cách gọi một máy nhắn tin để làm điều đó (máy nhắn tin đó hoạt động như thế catvì thiết bị xuất chuẩn của nó sẽ là một đường ống).

Tuy nhiên, trong khi ksh, $(< a < b)sẽ mở rộng sang nội dung của a, trong zshđó, nó sẽ mở rộng sang nội dung của ab(hoặc chỉ bkhi multiostùy chọn bị tắt), $(< a > b)sẽ sao chép avào bvà mở rộng thành không có gì, v.v.

bash có một toán tử tương tự nhưng với một vài khác biệt:

  • ý kiến ​​được cho phép trước nhưng không sau:

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

    hoạt động nhưng:

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

    mở rộng ra không có gì.

  • giống như trong zsh, chỉ có một tệp chuyển hướng stdin, mặc dù không có quay lại a $READNULLCMD, vì vậy $(<&3), $(< a < b)thực hiện chuyển hướng nhưng mở rộng thành không có gì.

  • vì một số lý do, trong khi bashkhông gọi cat, nó vẫn tạo ra một quy trình cung cấp nội dung của tệp thông qua một đường ống làm cho nó tối ưu hóa hơn nhiều so với các trình bao khác. Nó có hiệu lực giống như một $(cat < file)nơi catsẽ là một căn hộ cat.
  • do hậu quả của những điều trên, bất kỳ thay đổi nào được thực hiện bên trong đều bị mất sau đó ( $(<${file=foo.txt})ví dụ, trong phần được đề cập ở trên, $filenhiệm vụ đó sẽ bị mất sau đó).

Trong bash, IFS= read -rd '' var < file (cũng hoạt động trong zsh) là một cách hiệu quả hơn để đọc nội dung của tệp văn bản thành một biến. Nó cũng có lợi ích của việc bảo tồn các ký tự dòng mới. Xem thêm $mapfile[file]trong zsh(trong zsh/mapfilemô-đun và chỉ cho các tệp thông thường) cũng hoạt động với các tệp nhị phân.

Lưu ý rằng các biến thể dựa trên pdksh kshcó một vài biến thể so với ksh93. Quan tâm, trong mksh(một trong những vỏ có nguồn gốc pdksh), trong

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

được tối ưu hóa ở chỗ nội dung của tài liệu ở đây (không có các ký tự dấu) được mở rộng mà không sử dụng tệp tạm thời hoặc đường ống như trường hợp của các tài liệu ở đây, làm cho nó trở thành một cú pháp trích dẫn nhiều dòng hiệu quả.

Để có thể di động cho tất cả các phiên bản của ksh, zshbash, tốt nhất là giới hạn chỉ $(<file)tránh các bình luận và lưu ý rằng việc sửa đổi các biến được thực hiện trong phạm vi có thể hoặc không được bảo tồn.


Có đúng đó $(<)là một toán tử trên tên tập tin? Là <trong $(<)một toán tử chuyển hướng, hoặc không phải là một toán tử riêng, và phải là một phần của toàn bộ toán tử $(<)?
Tim

@Tim, không quan trọng bạn muốn gọi họ như thế nào. $(<file)có nghĩa là mở rộng sang nội dung filetheo cách tương tự như $(cat < file)vậy. Làm thế nào nó được thực hiện khác nhau từ vỏ này sang vỏ khác được mô tả ở độ dài trong câu trả lời. Nếu bạn thích, bạn có thể nói rằng đó là một toán tử đặc biệt được kích hoạt khi những gì trông giống như một sự thay thế lệnh (về mặt cú pháp) chứa những gì trông giống như một chuyển hướng stdin duy nhất (về mặt cú pháp), nhưng một lần nữa với sự cẩn thận và các biến thể tùy thuộc vào vỏ được liệt kê ở đây .
Stéphane Chazelas

@ StéphaneChazelas: Hấp dẫn, như thường lệ; Tôi đã đánh dấu cái này. Vì vậy, n<&mn>&mlàm điều tương tự? Tôi không biết điều đó, nhưng tôi đoán nó không quá ngạc nhiên.
Scott

@Scott, vâng, cả hai đều làm a dup(m, n). Tôi có thể thấy một số bằng chứng về ksh86 bằng cách sử dụng stdio và một số fdopen(fd, "r" or "w"), vì vậy nó có thể có vấn đề sau đó. Nhưng việc sử dụng stdio trong một chiếc vỏ có ý nghĩa rất nhỏ, vì vậy tôi không hy vọng bạn sẽ tìm thấy bất kỳ chiếc vỏ hiện đại nào, nơi sẽ tạo ra sự khác biệt. Một khác biệt là >&ndup(n, 1)(viết tắt 1>&n), trong khi <&ndup(n, 0)(viết tắt 0<&n).
Stéphane Chazelas

Đúng. Tất nhiên, ngoại trừ, dạng hai đối số của lệnh gọi sao chép mô tả tệp được gọi dup2(); dup()chỉ lấy một đối số và, như open(), sử dụng bộ mô tả tệp có sẵn thấp nhất. (Hôm nay tôi đã biết rằng có một dup3()chức năng .)
Scott

8

Bởi vì bashnó bên trong cho bạn, đã mở rộng tên tệp và chuyển tệp thành đầu ra tiêu chuẩn, giống như nếu bạn phải làm $(cat < filename). Đây là một tính năng bash, có lẽ bạn cần xem bashmã nguồn để biết chính xác nó hoạt động như thế nào.

Đây là chức năng xử lý tính năng này (Từ bashmã nguồn, tệp 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);
}

Một lưu ý $(<filename)không chính xác tương đương với $(cat filename); cái sau sẽ thất bại nếu tên tệp bắt đầu bằng dấu gạch ngang -.

$(<filename)ban đầu từ kshvà được thêm vào bashtừ Bash-2.02.


1
cat filenamesẽ thất bại nếu tên tệp bắt đầu bằng dấu gạch ngang vì mèo chấp nhận các tùy chọn. Bạn có thể làm việc xung quanh đó trên hầu hết các hệ thống hiện đại với cat -- filename.
Adam Katz

-1

Hãy nghĩ về việc thay thế lệnh như chạy một lệnh như bình thường và bỏ đầu ra tại điểm mà bạn đang chạy lệnh.

Đầu ra của các lệnh có thể được sử dụng làm đối số cho một lệnh khác, để đặt một biến và thậm chí để tạo danh sách đối số trong một vòng lặp for.

foo=$(echo "bar")sẽ đặt giá trị của biến $foothành bar; đầu ra của lệnh echo bar.

Bộ chỉ huy thay thế


1
Tôi tin rằng khá rõ ràng từ câu hỏi rằng OP hiểu những điều cơ bản về thay thế lệnh; câu hỏi là về trường hợp đặc biệt $(< file)và anh ta không cần hướng dẫn về trường hợp chung. Nếu bạn đang nói rằng đó $(< file)chỉ là một trường hợp thông thường $(command)với một mệnh lệnh < file, thì bạn đang nói điều tương tự Adam Katz đang nói , và cả hai bạn đều sai.
Scott
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.