Làm cách nào để sử dụng các dòng của tệp làm đối số của lệnh?


158

Nói, tôi có một tệp foo.txtchỉ định Nđối số

arg1
arg2
...
argN

mà tôi cần phải truyền cho lệnh my_command

Làm cách nào để sử dụng các dòng của tệp làm đối số của lệnh?


10
"đối số", số nhiều hay "đối số", đơn? Câu trả lời được chấp nhận chỉ đúng trong trường hợp một đối số - đó là những gì văn bản cơ thể thắc mắc - nhưng không phải trong trường hợp đa đối số, trong đó chủ đề xuất hiện để hỏi.
Charles Duffy

4 câu trả lời dưới đây thường có kết quả giống hệt nhau, tuy nhiên chúng có ngữ nghĩa hơi khác nhau. Bây giờ tôi nhớ tại sao tôi ngừng viết kịch bản bash: P
Navin

Câu trả lời:


236

Nếu shell của bạn là bash (trong số những cái khác), một phím tắt $(cat afile)$(< afile), vì vậy bạn sẽ viết:

mycommand "$(< file.txt)"

Tài liệu trong trang bash man trong phần 'Lệnh thay thế'.

Thay vào đó, có lệnh của bạn đọc từ stdin, vì vậy: mycommand < file.txt


11
Để được mô phạm, nó không phải là một phím tắt để sử dụng <toán tử. Điều đó có nghĩa là chính shell sẽ thực hiện chuyển hướng thay vì thực hiện catnhị phân cho các thuộc tính chuyển hướng của nó.
lstyls

2
Đó là cài đặt kernel, do đó bạn cần biên dịch lại kernel. Hoặc điều tra lệnh xargs
glenn jackman

3
@ykaner vì không có "$(…)", nội dung của file.txtsẽ được chuyển đến đầu vào tiêu chuẩn của mycommand, không phải là một đối số. "$(…)"có nghĩa là chạy lệnh và trả lại đầu ra dưới dạng một chuỗi ; Ở đây, lệnh Lệnh chỉ đọc các tập tin nhưng nó có thể phức tạp hơn.
törzsmókus 7/2/19

3
Nó không làm việc cho tôi trừ khi tôi mất dấu ngoặc kép. Với dấu ngoặc kép, nó lấy toàn bộ tệp làm 1 đối số. Không có dấu ngoặc kép, nó diễn giải mỗi dòng là một đối số riêng biệt.
Pete

1
@LarsH bạn hoàn toàn chính xác. Đó là một cách ít gây nhầm lẫn để đặt nó, cảm ơn.
lstyls

37

Như đã đề cập, bạn có thể sử dụng backticks hoặc $(cat filename).

Điều không được đề cập và tôi nghĩ điều quan trọng cần lưu ý là bạn phải nhớ rằng trình bao sẽ phân tách nội dung của tệp đó theo khoảng trắng, đưa ra từng "từ" mà nó tìm thấy cho lệnh của bạn làm đối số. Và mặc dù bạn có thể kèm theo một đối số dòng lệnh trong dấu ngoặc kép để nó có thể chứa khoảng trắng, các chuỗi thoát, v.v., việc đọc từ tệp sẽ không làm điều tương tự. Ví dụ: nếu tệp của bạn chứa:

a "b c" d

các đối số bạn sẽ nhận được là:

a
"b
c"
d

Nếu bạn muốn kéo từng dòng làm đối số, hãy sử dụng cấu trúc while / read / do:

while read i ; do command_name $i ; done < filename

Tôi nên đề cập, tôi giả sử rằng bạn đang sử dụng bash. Tôi nhận ra rằng có các shell khác ngoài đó, nhưng hầu như tất cả các máy * nix tôi đã làm việc trên bash hoặc một số tương đương. IIRC, cú pháp này sẽ hoạt động tương tự trên ksh và zsh.
Sẽ

1
Nên đọc read -rtrừ khi bạn muốn mở rộng các chuỗi thoát dấu gạch chéo ngược - và NUL là một dấu phân cách an toàn hơn để sử dụng so với dòng mới, đặc biệt nếu các đối số bạn chuyển qua là những thứ như tên tệp, có thể chứa các dòng mới. Ngoài ra, không xóa IFS, bạn sẽ bị xóa khoảng trắng hàng đầu và dấu vết i.
Charles Duffy

18
command `< file`

sẽ chuyển nội dung tệp cho lệnh trên stdin, nhưng sẽ loại bỏ các dòng mới, nghĩa là bạn không thể lặp lại từng dòng riêng lẻ. Vì vậy, bạn có thể viết một tập lệnh với vòng lặp 'for':

for line in `cat input_file`; do some_command "$line"; done

Hoặc (biến thể đa dòng):

for line in `cat input_file`
do
    some_command "$line"
done

Hoặc (biến thể nhiều dòng $()thay vì ``):

for line in $(cat input_file)
do
    some_command "$line"
done

Người giới thiệu:

  1. Đối với cú pháp vòng lặp: https://www.cyberciti.biz/faq/bash-for-loop/

Ngoài ra, đặt < filebackticks có nghĩa là nó thực sự không thực hiện chuyển hướng cho commandtất cả.
Charles Duffy

3
(Có phải những người đã nâng cấp điều này thực sự đã kiểm tra nó không? command `< file` Không hoạt động trong bash hoặc POSIX sh; zsh là một vấn đề khác, nhưng đó không phải là vấn đề mà câu hỏi này đề cập đến).
Charles Duffy

@CharlesDuffy Bạn có thể nói cụ thể hơn về cách nó không hoạt động không?
mwfearnley

@CharlesDuffy Điều này không hoạt động trong bash 3.2 và zsh 5.3. Điều này sẽ không hoạt động trong sh, tro và dash, nhưng không $ (<file).
Arnie97

1
@mwfearnley, rất vui khi cung cấp tính đặc hiệu đó. Mục tiêu là lặp đi lặp lại, phải không? Đặt Hello Worldtrên một dòng. Bạn sẽ thấy nó chạy đầu tiên some_command Hello, sau đó some_command World, không some_command "Hello World" hoặc some_command Hello World.
Charles Duffy

15

Bạn làm điều đó bằng cách sử dụng backticks:

echo World > file.txt
echo Hello `cat file.txt`

4
Điều này không tạo ra một đối số - bởi vì nó không được trích dẫn, nó có thể phân tách chuỗi, vì vậy nếu bạn phát ra echo "Hello * Starry * World" > file.txtở bước đầu tiên, bạn sẽ nhận được ít nhất bốn đối số riêng biệt được truyền cho lệnh thứ hai - và có khả năng hơn nữa, vì *s sẽ mở rộng thành tên của các tệp có trong thư mục hiện tại.
Charles Duffy

3
... và bởi vì nó đang chạy mở rộng toàn cầu, nên nó không phát ra chính xác những gì trong tệp. bởi vì nó đang thực hiện một hoạt động thay thế lệnh, nên nó cực kỳ kém hiệu quả - nó thực sự fork()lấy ra một lớp con với một phần tử gắn liền với thiết bị xuất chuẩn của nó, sau đó gọi /bin/catlà một đứa con của lớp con đó, sau đó đọc kết quả đầu ra thông qua FIFO; so sánh với $(<file.txt), trong đó đọc nội dung của tệp thành bash trực tiếp mà không có subshells hoặc FIFO liên quan.
Charles Duffy

11

Nếu bạn muốn thực hiện điều này một cách mạnh mẽ, hoạt động cho mọi đối số dòng lệnh có thể (giá trị có khoảng trắng, giá trị với dòng mới, giá trị với ký tự trích dẫn bằng chữ, giá trị không in được, giá trị với ký tự toàn cầu, v.v.), sẽ nhận được một chút thú vị hơn.


Để ghi vào một tệp, đưa ra một loạt các đối số:

printf '%s\0' "${arguments[@]}" >file

... thay bằng "argument one", "argument two"vv cho phù hợp.


Để đọc từ tệp đó và sử dụng nội dung của nó (trong bash, ksh93 hoặc shell khác gần đây với mảng):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

Để đọc từ tệp đó và sử dụng nội dung của nó (trong một vỏ không có mảng; lưu ý rằng điều này sẽ ghi đè lên danh sách đối số dòng lệnh cục bộ của bạn và do đó được thực hiện tốt nhất bên trong hàm, sao cho bạn ghi đè lên các đối số của hàm và không danh sách toàn cầu):

set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

Lưu ý rằng -d(cho phép sử dụng một dấu phân cách cuối dòng khác nhau) là một phần mở rộng không phải POSIX và một vỏ không có mảng cũng có thể không hỗ trợ nó. Nếu đó là trường hợp, bạn có thể cần sử dụng ngôn ngữ không vỏ để chuyển đổi nội dung được phân định bằng NUL thành evaldạng an toàn:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"

6

Đây là cách tôi chuyển nội dung của tệp dưới dạng đối số cho lệnh:

./foo --bar "$(cat ./bar.txt)"

1
Đây là - nhưng về cơ bản là kém hiệu quả hơn - tương đương hiệu quả với $(<bar.txt)(khi sử dụng shell, chẳng hạn như bash, với phần mở rộng thích hợp). $(<foo)là một trường hợp đặc biệt: không giống như thay thế lệnh thông thường, như được sử dụng trong $(cat ...), nó không tách ra một lớp con để hoạt động, và do đó tránh được rất nhiều chi phí.
Charles Duffy

3

Nếu tất cả những gì bạn cần làm là chuyển tệp arguments.txtcó nội dung

arg1
arg2
argN

vào my_command arg1 arg2 argNsau đó bạn có thể chỉ cần sử dụng xargs:

xargs -a arguments.txt my_command

Bạn có thể đặt các đối số tĩnh bổ sung trong xargscuộc gọi, như thế xargs -a arguments.txt my_command staticArgsẽ gọimy_command staticArg arg1 arg2 argN


1

Trong bash shell của tôi, những thứ sau đây hoạt động như một lá bùa:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

trong đó input_file là

arg1
arg2
arg3

Rõ ràng, điều này cho phép bạn thực thi nhiều lệnh với mỗi dòng từ input_file, một mẹo nhỏ hay mà tôi đã học ở đây .


3
"Bí quyết nhỏ" của bạn rất nguy hiểm từ góc độ bảo mật - nếu bạn có một đối số có chứa $(somecommand), bạn sẽ thực hiện lệnh đó thay vì chuyển qua dưới dạng văn bản. Tương tự như vậy, >/etc/passwdsẽ được xử lý dưới dạng chuyển hướng và ghi đè /etc/passwd(nếu được chạy với quyền phù hợp), v.v.
Charles Duffy

2
Thay vào đó, an toàn hơn nhiều khi thực hiện các thao tác sau (trên hệ thống có phần mở rộng GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- và cũng hiệu quả hơn, vì nó truyền càng nhiều đối số cho mỗi shell càng tốt thay vì bắt đầu một shell trên mỗi dòng trong tệp đầu vào của bạn.
Charles Duffy

Và tất nhiên, mất đi việc sử dụng vô dụngcat
tripleee

0

Sau khi chỉnh sửa câu trả lời của @Wesley Rice một vài lần, tôi quyết định những thay đổi của mình chỉ là quá lớn để tiếp tục thay đổi câu trả lời của anh ấy thay vì tự viết. Vì vậy, tôi quyết định tôi cần phải viết riêng của mình!

Đọc từng dòng của một tệp trong và hoạt động trên từng dòng như thế này:

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

Điều này đến trực tiếp từ tác giả Vivek Gite tại đây: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Anh ta nhận được tín dụng!

Cú pháp: Đọc từng dòng tệp trên shell Bash Unix & Linux:
1. Cú pháp như sau đối với bash, ksh, zsh và tất cả các shell khác để đọc một dòng tệp theo dòng
2. while read -r line; do COMMAND; done < input.file
3. -rTùy chọn được truyền để đọc lệnh ngăn chặn dấu gạch chéo ngược thoát khỏi bị giải thích.
4. Thêm IFS=tùy chọn trước khi đọc lệnh để ngăn khoảng trắng đầu / cuối bị cắt bớt -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


Và bây giờ để trả lời câu hỏi đã đóng này mà tôi cũng đã có: Có thể `git add` một danh sách các tệp từ một tệp không? - đây là câu trả lời của tôi:

Lưu ý rằng đó FILES_STAGEDlà một biến chứa đường dẫn tuyệt đối đến một tệp chứa một loạt các dòng trong đó mỗi dòng là một đường dẫn tương đối đến một tệp mà tôi muốn thực hiện git add. Đoạn mã này sắp trở thành một phần của tệp "eRCaGuy_dotfiles / hữu_scripts / sync_git numpo_to_build_machine.sh" trong dự án này, để cho phép đồng bộ hóa dễ dàng các tệp đang phát triển từ một PC (ví dụ: máy tính tôi mã hóa) Máy tính mạnh hơn tôi xây dựng trên): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

Người giới thiệu:

  1. Nơi tôi đã sao chép câu trả lời của mình từ: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Đối với cú pháp vòng lặp: https://www.cyberciti.biz/faq/bash-for-loop/

Liên quan:

  1. Cách đọc nội dung của từng dòng tệp và thực hiện git addtrên đó: Có thể `git add` một danh sách các tệp từ một tệp không?
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.