Chạy hai lệnh trên một đối số (không có kịch bản)


28

Làm thế nào tôi có thể thực hiện hai lệnh trên một đầu vào, mà không cần gõ đầu vào đó hai lần?

Ví dụ: lệnh stat cho biết rất nhiều về một tệp, nhưng không cho biết loại tệp của nó:

stat fileName

Lệnh tệp, cho biết loại tệp là gì:

file fileName

Bạn có thể thực hiện điều này theo một dòng theo cách này:

stat fileName ; file fileName

Tuy nhiên, bạn phải gõ fileName hai lần.

Làm thế nào bạn có thể thực hiện cả hai lệnh trên cùng một đầu vào (mà không cần nhập đầu vào hoặc một biến của đầu vào hai lần)?

Trong Linux, tôi biết làm thế nào để đầu ra đường ống, nhưng làm thế nào để bạn đầu vào đường ống?


14
Nói rõ hơn, đó không phải là "đầu vào", chúng là đối số (còn gọi là tham số). Đầu vào có thể được dẫn qua <(đầu vào từ tệp ở bên trái) hoặc |(đầu vào từ luồng sang bên phải). Có một sự khác biệt.
goldilocks

6
Không phải là câu trả lời cho câu hỏi của bạn, nhưng có thể là câu trả lời cho vấn đề của bạn: Trong bash, yank-last-arglệnh (phím tắt mặc định Meta-.hoặc Meta-_; Metathường là Altphím bên trái ) sẽ sao chép tham số cuối cùng từ dòng lệnh trước vào dòng lệnh hiện tại. Vì vậy, sau khi thực hiện, stat fileNamebạn gõ file [Meta-.]và không phải gõ fileNamelại. Tôi luôn luôn sử dụng nó.
Dubu

2
Và sự khác biệt là <>được chuyển hướng , không phải đường ống . Một đường ống kết nối hai quá trình, trong khi chuyển hướng chỉ đơn giản là gán lại stdin / stdout.
bsd

@bdowning: Vâng, nhưng bạn đang chuyển hướng đường ống mặc dù không phải là bạn. Bạn có thể chuyển hướng bắt đầu của một đường ống để đọc từ một tệp trên đĩa hoặc bạn có thể chuyển hướng kết thúc của một đường ống để ghi vào một tệp trên đĩa. Vẫn còn đường ống. ;-)
James Haigh

Câu trả lời:


22

Đây là một cách khác:

$ stat filename && file "$_"

Thí dụ:

$ stat /usr/bin/yum && file "$_"
  File: ‘/usr/bin/yum’
  Size: 801         Blocks: 8          IO Block: 4096   regular file
Device: 804h/2052d  Inode: 1189124     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:rpm_exec_t:s0
Access: 2014-06-11 22:55:53.783586098 +0700
Modify: 2014-05-22 16:49:35.000000000 +0700
Change: 2014-06-11 19:15:30.047017844 +0700
 Birth: -
/usr/bin/yum: Python script, ASCII text executable

Điều đó làm việc trong bashzsh. Điều đó cũng hoạt động trong mkshdashchỉ khi tương tác. Trong AT & T ksh, điều đó chỉ hoạt động khi file "$_"nằm trên một dòng khác với dòng stat.


1
Cái !$này sẽ không hoạt động vì !$mở rộng đến từ cuối cùng của sự kiện lịch sử cuối cùng (vì vậy dòng lệnh đã nhập trước đó, không phải của lệnh stat).
Stéphane Chazelas

@ StéphaneChazelas: Ồ, vâng, lỗi của tôi, tôi đã xóa nó.
cuonglm

32

Có lẽ sẽ khiến các đốt ngón tay của tôi bị rách vì điều này, đây là một sự kết hợp đầy thách thức của việc mở rộng bash brace và eval dường như thực hiện mánh khóe

eval {stat,file}" fileName;"


2
mở rộng cú đúp có nguồn gốc trong csh, không bash. bashsao chép nó từ ksh. Mã đó sẽ hoạt động trong tất cả các csh, tcsh, ksh, zsh, cá và bash.
Stéphane Chazelas

1
Quá tệ, bạn mất khả năng "tab ra" (tự động hoàn thành) tên tệp, do trích dẫn.
Lonniebiz

Xin lỗi, nhưng tôi không thể cưỡng lại một trong hai
tomd

Xin lỗi, vấn đề ở đây là evalgì? (tham khảo các bình luận hàng đầu)
user13107 11/03/2016

28

Với zsh, bạn có thể sử dụng các chức năng ẩn danh:

(){stat $1; file $1} filename

Với eslambdas:

@{stat $1; file $1} filename

Bạn cũng có thể làm:

{ stat -; file -;} < filename

(làm statđầu tiên làfile sẽ cập nhật thời gian truy cập).

Tôi sẽ làm:

f=filename; stat "$f"; file "$f"

Mặc dù, đó là những gì các biến là cho.


3
{ stat -; file -;} < filenamelà ma thuật. statphải kiểm tra xem stdin của nó có được kết nối với một tệp và báo cáo các thuộc tính của tệp không? Có lẽ không tiến bộ con trỏ trên stdin do đó cho phép fileđánh hơi nội dung stdin
iruvar

1
@ 1_CR, vâng. stat -nói statđể làm một fstat()trên fd 0.
Stéphane Chazelas

4
Các { $COMMAND_A -; $COMMAND_B; } < filenamebiến thể sẽ không làm việc trong trường hợp tổng quát nếu $ COMMAND_A thực sự đọc bất kỳ đầu vào; ví dụ { cat -; cat -; } < filenamesẽ chỉ in nội dung của tên tệp một lần.
Max Nanasy

2
stat -được xử lý đặc biệt kể từ coreutils-8.0 (tháng 10 năm 2009), trong các phiên bản trước đó bạn sẽ gặp lỗi (trừ khi bạn có một tệp được gọi -từ một thử nghiệm trước đó, trong trường hợp đó bạn sẽ nhận được kết quả sai ...)
mr .spuratic

1
@ mr.spuratic. Có, và các số liệu thống kê BSD, IRIX và zsh cũng sẽ không nhận ra điều đó. Với zsh và IRIX stat, bạn có thể sử dụng stat -f0thay thế.
Stéphane Chazelas

9

Tất cả những câu trả lời này có vẻ giống như kịch bản đối với tôi, ở mức độ lớn hơn hoặc ít hơn. Đối với một giải pháp thực sự không liên quan đến kịch bản và giả sử rằng bạn đang ngồi trên bàn phím gõ vào một cái vỏ, bạn có thể thử:

somefile stat Enter
tập tin Esc_   Enter

Trong bash, zsh và ksh ít nhất, trong cả hai chế độ vi và emacs, nhấn Escape và sau đó gạch dưới sẽ chèn từ cuối cùng từ dòng trước vào bộ đệm chỉnh sửa cho dòng hiện tại.

Hoặc, bạn có thể sử dụng thay thế lịch sử:

stat Enter
tập tin nào đó! $Enter

Trong bash, zsh, csh, tcsh và hầu hết các shell khác, !$được thay thế bằng từ cuối cùng của dòng lệnh trước đó khi dòng lệnh hiện tại được phân tích cú pháp.


Những tùy chọn khác có sẵn trong zsh / bash là như thế Esc _nào? Bạn có thể vui lòng liên kết với các tài liệu chính thức?
Abhijeet Rastogi


1
zsh: linux.die.net/man/1/zshzle và tìm kiếm "insert-last-word"
godlygeek

1
@shadyabhi ^ Liên kết cho cả keybindings tiêu chuẩn của bash và zsh. Lưu ý rằng bash chỉ sử dụng readline, vì vậy (hầu hết) các bash sẽ hoạt động trong bất kỳ ứng dụng nào sử dụng thư viện readline.
trời ơi

3

Cách tôi đã tìm thấy để làm điều này một cách hợp lý chỉ đơn giản là sử dụng xargs, nó lấy một tệp / ống và chuyển đổi nội dung của nó thành một đối số chương trình. Điều này có thể được kết hợp với tee chia tách một luồng và gửi nó đến hai hoặc nhiều chương trình, Trong trường hợp của bạn, bạn cần:

echo filename | tee >(xargs stat) >(xargs file) | cat

Không giống như nhiều câu trả lời khác, câu trả lời này sẽ hoạt động trong bash và hầu hết các shell khác trong Linux. Tôi sẽ đề nghị đây là trường hợp sử dụng tốt của một biến nhưng nếu bạn hoàn toàn không thể sử dụng một biến thì đây là cách tốt nhất để làm điều đó chỉ với các đường ống và các tiện ích đơn giản (giả sử bạn không có vỏ đặc biệt lạ mắt).

Ngoài ra, bạn có thể làm điều này cho bất kỳ số lượng chương trình nào bằng cách thêm chúng theo cách tương tự như hai chương trình này sau khi phát bóng.

CHỈNH SỬA

Như được đề xuất trong các ý kiến ​​có một vài sai sót trong câu trả lời này, đầu tiên là tiềm năng cho sự xen kẽ đầu ra. Điều này có thể được sửa chữa như vậy:

echo filename | tee >(xargs stat) >(( wait $!; xargs file )) | cat

Điều này sẽ buộc các lệnh chạy lần lượt và đầu ra sẽ không bao giờ được xen kẽ.

Vấn đề thứ hai là tránh thay thế quá trình không có sẵn trong một số shell, điều này có thể đạt được bằng cách sử dụng tpipe thay vì tee như vậy:

echo filename | tpipe 'xargs stat' | ( wait $!; xargs file )

Điều này sẽ có thể mang theo gần như mọi vỏ (tôi hy vọng) và giải quyết các vấn đề trong phần thưởng khác, tuy nhiên tôi đang viết cái cuối cùng này từ bộ nhớ vì hệ thống hiện tại của tôi không có sẵn tpipe.


1
Thay thế quy trình là một tính năng ksh chỉ hoạt động trong ksh(không phải các biến thể miền công cộng) bashzsh. Lưu ý rằng statfilesẽ chạy đồng thời, vì vậy có thể đầu ra của chúng sẽ được đan xen (Tôi cho rằng bạn đang sử dụng | catđể buộc bộ đệm đầu ra để hạn chế hiệu ứng đó?).
Stéphane Chazelas

@ StéphaneChazelas Bạn đã đúng về con mèo, khi sử dụng nó tôi chưa từng quản lý để tạo ra một trường hợp đầu vào xen kẽ khi sử dụng nó. Tuy nhiên tôi sẽ thử một cái gì đó để tạo ra một giải pháp di động hơn trong giây lát.
Vality

3

Câu trả lời này tương tự như một vài câu hỏi khác:

tên tệp stat   && tập tin! #: 1

Không giống như các câu trả lời khác, tự động lặp lại đối số cuối cùng từ lệnh trước đó, câu hỏi này yêu cầu bạn phải đếm:

Tên tệp ls -ld   && tệp! #: 2

Không giống như một số câu trả lời khác, điều này không yêu cầu trích dẫn:

stat "useless cat"  &&  file !#:1

hoặc là

echo Sometimes a '$cigar' is just a !#:3.

2

Điều này tương tự với một vài câu trả lời khác, nhưng dễ mang theo hơn câu trả lời này và đơn giản hơn nhiều so với câu trả lời này :

sh -c 'stat "$ 0"; tập tin "$ 0"' filename

hoặc là

sh -c 'stat "$ 1"; tập tin "$ 1"' sh filename

trong đó shđược gán cho $0. Mặc dù phải nhập thêm ba ký tự, nhưng hình thức (thứ hai) này thích hợp hơn vì đó $0là tên bạn đặt cho tập lệnh nội tuyến đó. Ví dụ, nó được sử dụng trong các thông báo lỗi của shell cho tập lệnh đó, như khi nào filehoặc statkhông thể tìm thấy hoặc không thể được thực thi vì bất kỳ lý do nào.


Nếu bạn muốn làm

stat filename 1  filename 2  filename 3 ...; tập tin filename 1  filename 2  filename 3 ...

cho một số lượng tập tin tùy ý, làm

sh -c 'stat "$ @"; tệp "$ @" 'sh tên tệp 1  tên tệp 2  tên tệp 3 ...

mà hoạt động vì "$@"tương đương với "$1" "$2" "$3" ….


Đó $0là tên bạn muốn đặt cho tập lệnh nội tuyến đó. Ví dụ, nó được sử dụng trong các thông báo lỗi bởi shell cho tập lệnh đó. Giống như khi filehoặc statkhông thể được tìm thấy hoặc không thể được thực thi vì bất kỳ lý do. Vì vậy, bạn muốn một cái gì đó có ý nghĩa ở đây. Nếu không có cảm hứng, chỉ cần sử dụng sh.
Stéphane Chazelas

1

Tôi nghĩ rằng bạn đang hỏi một câu hỏi sai, ít nhất là cho những gì bạn đang cố gắng làm. statfilecác lệnh đang lấy tham số là tên của tệp chứ không phải nội dung của tệp. Sau này là lệnh đọc tệp được xác định bởi tên bạn đang chỉ định.

Một đường ống được cho là có hai đầu, một đầu được sử dụng làm đầu vào và một đầu khác làm đầu ra và điều này có ý nghĩa vì bạn có thể cần phải xử lý khác nhau trên đường đi với các công cụ khác nhau cho đến khi bạn nhận được kết quả cần thiết. Nói rằng bạn biết làm thế nào để đầu ra đường ống nhưng không biết làm thế nào để đầu vào đường ống không đúng về nguyên tắc.

Trong trường hợp của bạn, bạn hoàn toàn không cần một đường ống vì bạn phải cung cấp đầu vào dưới dạng tên tệp. Và ngay cả khi những công cụ đó ( statfile) sẽ đọc stdin, ống không liên quan ở đây một lần nữa vì đầu vào không nên bị thay đổi bởi bất kỳ công cụ nào khi nó đến công cụ thứ hai.


1

Điều này sẽ làm việc cho tất cả các tên tập tin trong thư mục hiện tại.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *

1
Giả sử tên tệp không chứa trích dẫn kép, dấu gạch chéo ngược, đô la, backtick, }... ký tự và không bắt đầu bằng -. Một số printftriển khai (zsh, bash, ksh93) có a %q. Với zsh, đó có thể là:printf 'stat %q; file %1$q\n' ./* | zsh
Stéphane Chazelas

@StephaneChezales - tất cả điều đó đã được sửa bây giờ ngoại trừ khả năng của hardquote. Tôi có thể xử lý điều đó với một lần sed s///tôi đoán, nhưng đó là về phạm vi - và nếu mọi người đưa nó vào tên tệp của họ thì họ nên sử dụng windows.
mikeerv

@ StéphaneChazelas - nevermind - Tôi quên mất cách dễ dàng để xử lý chúng.
mikeerv

Nói một cách chính xác, đây không phải là một câu trả lời cho câu hỏi như đã hỏi: Làm sao bạn có thể thực thi cả hai lệnh trên cùng một đầu vào ( mà không cần gõ đầu vào hai lần )? "(Nhấn mạnh thêm). Câu trả lời của bạn áp dụng cho tất cả các tệp trong thư mục hiện tại - và nó bao gồm *hai lần . Bạn cũng có thể nói stat -- *; file -- *.
Scott

@Scott - đầu vào không được gõ hai lần - *được mở rộng. Các mở rộng của *cộng hai lệnh cho mỗi trong tranh luận * là đầu vào. Xem? printf commands\ %s\;shift *
mikeerv

1

Có một vài tài liệu tham khảo khác nhau về 'đầu vào' ở đây, vì vậy tôi sẽ đưa ra một vài tình huống với sự hiểu biết về nó trước tiên. Để trả lời nhanh cho câu hỏi của bạn ở dạng ngắn nhất :

stat testfile < <($1)> outputfile

Ở trên sẽ thực hiện một chỉ số trên testfile, lấy (chuyển hướng) nó STDOUT và đưa nó vào hàm đặc biệt tiếp theo (phần <()) sau đó đưa ra kết quả cuối cùng của bất cứ thứ gì, vào một tệp mới (tệp đầu ra). Tệp được gọi, sau đó được tham chiếu với bash dựng sẵn ($ 1 mỗi lần sau đó, cho đến khi bạn bắt đầu một bộ hướng dẫn mới).

Câu hỏi của bạn rất hay, và có một số câu trả lời và cách để làm điều này, nhưng nó thực sự thay đổi với những gì bạn đang làm cụ thể.

Chẳng hạn, bạn cũng có thể lặp lại, điều này khá tiện dụng. Một cách sử dụng phổ biến của điều này là, trong tư duy mã psuedo, là:

run program < <($output_from_program)> my_own.log

Đưa nó vào và mở rộng kiến ​​thức đó cho phép bạn tạo ra những thứ như:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

Điều này sẽ thực hiện một ls -A đơn giản trên thư mục hiện tại của bạn, sau đó cho biết trong khi lặp qua từng kết quả từ ls -A đến (và đây là nơi khó khăn!) Grep "từ đó" trong mỗi kết quả đó và chỉ thực hiện trước đó printf (màu đỏ) nếu nó thực sự tìm thấy một tập tin có "thatword" trong đó. Nó cũng sẽ ghi lại kết quả của grep vào một tệp văn bản mới, files_that_matched_thatword.

Ví dụ đầu ra:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

index.html

Tất cả điều đó chỉ đơn giản là in kết quả ls -A, không có gì đặc biệt. Thêm một cái gì đó cho nó để grep lần này:

echo "thatword" >> newfile

Bây giờ chạy lại nó:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

files_that_matched_thatword  index.html  newfile

Found a file: newfile:thatword

Mặc dù có lẽ là một câu trả lời mệt mỏi hơn bạn đang tìm kiếm hiện tại, tôi tin rằng việc lưu giữ những ghi chú tiện dụng như thế này sẽ giúp ích cho bạn nhiều hơn trong những nỗ lực trong tương lai.

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.