Tại sao lệnh không ls | Tập tin làm việc?


32

Tôi đã nghiên cứu về dòng lệnh và được biết rằng |(đường ống) có nghĩa là chuyển hướng đầu ra từ một lệnh sang đầu vào của một lệnh khác. Vậy tại sao lệnh ls | filekhông hoạt động?

file đầu vào là một trong những tên tệp, như file filename1 filename2

lsđầu ra là một danh sách các thư mục và tệp trên một thư mục, vì vậy tôi nghĩ rằng nó ls | fileđược cho là hiển thị loại tệp của mỗi tệp trên một thư mục.

Tuy nhiên, khi tôi sử dụng nó, đầu ra là:

    Usage: file [-bcEhikLlNnprsvz0] [--apple] [--mime-encoding] [--mime-type]
        [-e testname] [-F separator] [-f namefile] [-m magicfiles] file ...
    file -C [-m magicfiles]
    file [--help]

Vì có một số lỗi với việc sử dụng filelệnh


2
Nếu bạn đang sử dụng đơn giản ls, nó cho biết rằng bạn muốn tất cả các tệp trong thư mục hiện tại được xử lý bằng filelệnh. ... Vậy tại sao không đơn giản làm : file *, nó sẽ trả lời bằng một dòng cho mọi tệp, thư mục.
Knud Larsen

file *là cách thông minh nhất, tôi chỉ tự hỏi tại sao sử dụng lsđầu ra không hoạt động.
Xóa

6
Tiền đề là thiếu sót: "đầu vào tệp là một trong những tên tệp khác, như tệp filename1 filename2" Đó không phải là đầu vào. Đó là những đối số dòng lệnh, như @John Kugelman chỉ ra bên dưới.
Monty Harder

3
Tiếp theo, phân tích cú phápls nói chung là một ý tưởng tồi.
kojiro

Câu trả lời:


71

Vấn đề cơ bản là filemong đợi tên tệp dưới dạng đối số dòng lệnh, không phải trên stdin. Khi bạn viết ls | fileđầu ra của lsđang được chuyển làm đầu vào file. Không phải là đối số, là đầu vào.

Có gì khác biệt?

  • Đối số dòng lệnh là khi bạn viết cờ và tên tệp sau một lệnh, như trong cmd arg1 arg2 arg3. Trong kịch bản shell những lập luận này có sẵn như là các biến $1, $2, $3, vv Trong C bạn muốn truy cập chúng thông qua char **argvint argclập luận để main().

  • Đầu vào tiêu chuẩn, stdin, là một luồng dữ liệu. Một số chương trình thích cathoặc wcđọc từ stdin khi chúng không được cung cấp bất kỳ đối số dòng lệnh nào. Trong một kịch bản shell, bạn có thể sử dụng readđể có được một dòng đầu vào. Trong C, bạn có thể sử dụng scanf()hoặc getchar(), trong số các tùy chọn khác nhau.

filethường không đọc từ stdin. Nó hy vọng ít nhất một tên tệp sẽ được chuyển qua làm đối số. Đó là lý do tại sao nó in ra cách sử dụng khi bạn viết ls | file, bởi vì bạn đã không vượt qua một đối số.

Bạn có thể sử dụng xargsđể chuyển đổi stdin thành đối số, như trong ls | xargs file. Tuy nhiên, như terdon đề cập , phân tích cú pháp lslà một ý tưởng tồi. Cách trực tiếp nhất để làm điều này chỉ đơn giản là:

file *

2
Hoặc buộc filephải lấy tên tệp từ đầu vào của nó, sử dụng ls | file -f -. Vẫn là một ý tưởng tồi tệ.
quang phổ

2
@Braiam> Đó là điểm chính. Và lsđầu ra của ống dẫn vào filestdin. Hãy thử nó.
quang phổ

4
@Braiam> Thật là lãng phí và nguy hiểm. Nhưng nó hoạt động và thật tuyệt khi có nó để so sánh với các tùy chọn tốt hơn nếu OP đang học cách sử dụng chuyển hướng. Để hoàn thiện tôi cũng có thể đề cập file $(ls), cũng hoạt động, theo một cách khác.
quang phổ

2
Tôi nghĩ sau khi đọc tất cả các câu trả lời tôi có một bức tranh lớn hơn về vấn đề này, mặc dù tôi nghĩ rằng tôi sẽ cần đọc thêm để thực sự hiểu tất cả. Đầu tiên, rõ ràng sử dụng đường ống và chuyển hướng không phân tích đầu ra dưới dạng đối số , mà là STDIN . Tôi vẫn phải đọc thêm để hiểu rõ hơn, nhưng tạo ra một đối số tìm kiếm hời hợt có vẻ như văn bản được phân tích cú pháp cho chương trình trong một mảng và STDIN giống như cách gộp thông tin cho tệp hoặc đầu ra (không phải tất cả các chương trình được thiết kế để làm việc với "tổng hợp" này)
IanC

3
Thứ hai, sử dụng ls để tạo danh sách tên tệp có vẻ là một ý tưởng tồi, bởi vì các ký tự đặc biệt được chấp nhận trên tên tệp nhưng có thể dẫn đến kết quả sai lệch trên ls . Vì nó sử dụng dòng mới làm dấu phân cách giữa tên tệp và tên tệp có thể chứa dòng mới và các ký tự đặc biệt khác, đầu ra cuối cùng có thể không chính xác.
IanC

18

Bởi vì, như bạn nói, đầu vào của filephải là tên tệp . Đầu ra của ls, tuy nhiên, chỉ là văn bản. Rằng nó là một danh sách các tên tệp không thay đổi thực tế rằng nó chỉ là văn bản và không phải là vị trí của các tệp trên ổ cứng.

Khi bạn thấy đầu ra được in trên màn hình, những gì bạn thấy là văn bản. Cho dù văn bản đó là một bài thơ hay một danh sách tên tệp không làm nên sự khác biệt cho máy tính. Tất cả những gì nó biết là nó là văn bản. Đây là lý do tại sao bạn có thể chuyển đầu ra của lscác chương trình lấy văn bản làm đầu vào (mặc dù bạn thực sự, thực sự không nên ):

$ ls / | grep etc
etc

Vì vậy, để sử dụng đầu ra của một lệnh liệt kê tên tệp dưới dạng văn bản (chẳng hạn như lshoặc find) làm đầu vào cho lệnh lấy tên tệp, bạn cần sử dụng một số thủ thuật. Công cụ điển hình cho việc này là xargs:

$ ls
file1 file2

$ ls | xargs wc
 9  9 38 file1
 5  5 20 file2
14 14 58 total

Như tôi đã nói trước đây, bạn thực sự không muốn phân tích cú pháp đầu ra ls. Một cái gì đó như findlà tốt hơn (các print0bản in một \0thay vì một newilne sau mỗi tên tập tin và -0các xargsphép nó đối phó với đầu vào như vậy, đây là một thủ thuật để làm cho lệnh công việc của bạn với tên tập tin chứa ký tự dòng mới):

$ find . -type f -print0 | xargs -0 wc
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Mà cũng có cách riêng để làm việc này, mà không cần xargsgì cả:

$ find . -type f -exec wc {} +
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Cuối cùng, bạn cũng có thể sử dụng một vòng lặp shell. Tuy nhiên, lưu ý rằng trong hầu hết các trường hợp, xargssẽ nhanh hơn và hiệu quả hơn nhiều. Ví dụ:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2

Một phụ vấn đề là filekhông xuất hiện để thực sự đọc stdin trừ khi đưa ra một rõ ràng -giữ chỗ: so sánh file foo, echo foo | fileecho foo | file -; trong thực tế, đó có thể là lý do cho thông báo sử dụng trong trường hợp OP (nghĩa là không thực sự vì đầu ra của ls"đơn giản là văn bản", mà là vì danh sách đối số filetrống)
Steel

@steel ấn vâng. AFAIK đó là trường hợp cho tất cả các chương trình mong đợi các tệp và không phải là văn bản làm đầu vào. Họ chỉ bỏ qua stdin theo mặc định. Lưu ý rằng echo foo | file -không thực sự chạy filetrên tệp foonhưng trên luồng stdin.
terdon

Vâng, có những con vịt kỳ lạ (?!) Như thế catngoại trừ stdin mà không -ngoại trừ khi đưa ra các đối số tập tin như tôi nghĩ?
Steel

3
Câu trả lời này không giải thích được sự khác biệt giữa stdin và đối số dòng lệnh, và vì vậy, mặc dù có nhiều điểm hơn câu trả lời được chấp nhận, vẫn gây hiểu lầm sâu sắc vì cùng một lý do.
zwol

5
@terdon Tôi nghĩ đó là một lỗi nghiêm trọng trong trường hợp này. "Tệp (1) lấy danh sách các tệp để hoạt động dưới dạng đối số dòng lệnh, không phải là đầu vào tiêu chuẩn" là cơ bản để hiểu lý do tại sao lệnh của OP không hoạt động và sự khác biệt là cơ bản đối với kịch bản lệnh shell nói chung; bạn không làm cho họ bất kỳ ưu đãi bằng cách phủ lên nó.
zwol

6

học được rằng '|' (đường ống) có nghĩa là chuyển hướng đầu ra từ một lệnh sang đầu vào của một lệnh khác.

Nó không "chuyển hướng" đầu ra, nhưng lấy đầu ra của một chương trình và sử dụng nó làm đầu vào, trong khi tệp không lấy đầu vào mà là tên tệp làm đối số , sau đó được kiểm tra. Redirections không vượt qua các tên tệp như các đối số không phải đường ống không, sau này những gì bạn đang làm.

Những gì bạn có thể làm là đọc tên tệp từ một tệp với --files-fromtùy chọn nếu bạn có một tệp liệt kê tất cả các tệp bạn muốn kiểm tra, nếu không, chỉ cần chuyển các đường dẫn đến tệp của bạn dưới dạng đối số.


6

Câu trả lời được chấp nhận giải thích tại sao lệnh ống không hoạt động ngay lập tức và với file *lệnh, nó cung cấp một giải pháp đơn giản, đơn giản.

Tôi muốn đề xuất một giải pháp thay thế khác có thể có ích vào một lúc nào đó. Bí quyết là sử dụng (`)nhân vật backtick . Backtick được giải thích rất chi tiết ở đây . Nói tóm lại, nó lấy đầu ra của lệnh được đặt trong backticks và thay thế nó thành một chuỗi vào lệnh còn lại.

Vì vậy, find `ls`sẽ lấy đầu ra của lslệnh và thay thế nó làm đối số cho findlệnh. Điều này dài hơn và phức tạp hơn giải pháp được chấp nhận, nhưng các biến thể của điều này có thể hữu ích trong các tình huống khác.


Tôi đang đọc một cuốn sách về việc sử dụng dòng lệnh trên Linux (sự nghi ngờ xuất phát từ việc tôi đang thử nghiệm nó), và trùng hợp là tôi vừa đọc về "thay thế lệnh". Bạn có thể sử dụng $ (lệnh) hoặc command(không thể tìm thấy mã dấu gạch chéo ngược trên điện thoại của tôi) để mở rộng đầu ra của lệnh trong bash và sử dụng nó làm tham số cho các lệnh khác. Thực sự hữu ích, mặc dù sử dụng nó trong trường hợp này (với ls ) vẫn sẽ dẫn đến một số vấn đề vì các ký tự đặc biệt trên một số tên tệp.
IanC

@IanC Thật không may, hầu hết các cuốn sách và hướng dẫn về bash đều là rác rưởi, bị bôi nhọ bởi các thực tiễn xấu, cú pháp không được chấp nhận, các lỗi tinh vi; (duy nhất) các tài liệu tham khảo đáng tin cậy ngoài kia là các nhà phát triển bash, nghĩa là hướng dẫnkênh #bash IRC trên freenode (cũng kiểm tra các tài nguyên được liên kết trong chủ đề kênh).
ignis 7/07/2016

1
Sử dụng thay thế lệnh đôi khi có thể thực sự hữu ích, nhưng trong bối cảnh này, nó khá là sai lầm - đặc biệt là với ls.
Joe


5

Đầu ra của lsmột đường ống là một khối dữ liệu vững chắc với 0x0a phân tách từng dòng - tức là một ký tự dòng - và filenhận đây là một tham số, trong đó nó mong muốn nhiều ký tự hoạt động cùng một lúc.

Theo nguyên tắc chung, không bao giờ sử dụng lsđể tạo nguồn dữ liệu cho các lệnh khác - một ngày nào đó nó sẽ dẫn .. vào rmvà sau đó bạn gặp rắc rối!

Tốt hơn là sử dụng một vòng lặp, chẳng hạn như for i in *; do file "$i" ; donesẽ tạo ra đầu ra mà bạn muốn, có thể dự đoán được. Các trích dẫn là có trong trường hợp tên tập tin với không gian.


8
dễ dàng hơn: file *;-)
Wayne_Yux 6/07/2016

3
@IanC Tôi thực sự không thể nhấn mạnh đủ rằng phân tích đầu ra lslà một ý tưởng rất, rất xấu . Không chỉ bởi vì bạn có thể chuyển nó sang một cái gì đó có hại như rm, quan trọng hơn bởi vì nó phá vỡ bất kỳ tên tệp không chuẩn nào.
terdon

5
Đoạn đầu tiên là một nơi nào đó giữa sai lệch và thẳng vô nghĩa. Nguồn cấp dữ liệu không có liên quan. Đoạn thứ hai đúng vì lý do sai. Thật tệ khi phân tích ls, nhưng không phải vì nó có thể được "dẫn đường" một cách kỳ diệu đến rm.
John Kugelman hỗ trợ Monica

1
rmtên tập tin từ đầu vào tiêu chuẩn? Tôi nghĩ là không. Ngoài ra, theo nguyên tắc chung, lslà một trong những ví dụ chính về nguồn dữ liệu cho việc sử dụng các đường ống Unix kể từ khi bắt đầu Unix. Đó là lý do tại sao nó mặc định là một tên tệp đơn giản trên mỗi dòng không có thuộc tính hoặc trang sức khi đầu ra của nó là một đường ống, không giống như định dạng mặc định thông thường của nó khi đầu ra là đầu cuối.
davidbak

2
@DewiMorgan Trang web này chủ yếu nhắm vào đối tượng không có kỹ thuật, vì vậy việc truyền bá / khuyến khích những thói quen xấu ở đây không gây hại và không có gì tốt. Trên unix.SE hoặc cộng đồng công nghệ khác, những người dùng có kiến ​​thức / phương tiện để nhắm rất gần bàn chân của họ mà không tự bắn vào chân mình, quan điểm của bạn có thể giữ (liên quan đến các thực tiễn khác) nhưng ở đây nó không làm cho nhận xét của bạn trông thông minh.
Ignis

4

Nếu bạn muốn sử dụng một đường ống để cung cấp, hãy filesử dụng tùy chọn -fthường được theo sau bởi tên tệp nhưng bạn cũng có thể sử dụng một dấu gạch nối duy nhất -để đọc từ stdin, vì vậy

$ ls
cow.pdf  some.txt
$ ls | file -f -
cow.pdf:       PDF document, version 1.4
some.txt:        ASCII text

Thủ thuật với dấu gạch nối -hoạt động với rất nhiều tiện ích dòng lệnh tiêu chuẩn (mặc dù --đôi khi), vì vậy nó luôn đáng để thử.

Công cụ xargnày mạnh hơn nhiều và trong hầu hết các trường hợp chỉ cần nếu danh sách đối số quá dài (xem bài đăng này để biết chi tiết).


Khi --nào vậy Tôi chưa bao giờ thấy điều đó. --thường là chỉ báo "kết thúc cờ".
John Kugelman hỗ trợ Monica

Có, nhưng tôi đã tìm thấy nó trong một vài trường hợp (ab) được lập trình viên sử dụng theo cách đó. Tôi không thể nhớ chính xác nơi nào (sẽ thêm một bình luận nếu tôi làm) nhưng tôi nhớ những lời nguyền mà tôi đã thốt ra khi tôi phát hiện ra và những lời nguyền này chắc chắn là NSFW ;-)
deamentiaemundi

2

Nó hoạt động sử dụng lệnh như dưới đây

ls | xargs file

Nó sẽ làm việc tốt hơn với tôi


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.