Làm thế nào để viết một tập lệnh chấp nhận đầu vào từ một tập tin hoặc từ stdin?


57

Làm thế nào người ta có thể viết một tập lệnh chấp nhận đầu vào từ một đối số tên tệp hoặc từ stdin?

ví dụ, bạn có thể sử dụng lesscách này người ta có thể thực hiện less filenamevà tương đương cat filename | less.

Có cách nào dễ dàng để làm như vậy không? hoặc tôi cần phải phát minh lại bánh xe và viết một chút logic trong kịch bản?


@PlasmaPower Miễn là câu hỏi thuộc chủ đề về SU, không có yêu cầu phải hỏi trên một trang SE khác. Rất nhiều trang SE có sự trùng lặp; nói chung, chúng tôi không cần đề xuất một trang web chồng chéo trừ khi câu hỏi là ngoài chủ đề (trong trường hợp đó, bỏ phiếu để di chuyển) hoặc theo chủ đề nhưng không nhận được nhiều phản hồi (trong trường hợp đó, người hỏi nên gắn cờ cho người điều hành- chú ý / di chuyển, không chéo bài).
Bob

Câu trả lời:


59

Nếu đối số tệp là đối số đầu tiên cho tập lệnh của bạn, hãy kiểm tra xem có đối số ( $1) và đó là tệp không. Khác đọc đầu vào từ stdin -

Vì vậy, tập lệnh của bạn có thể chứa một cái gì đó như thế này:

#!/bin/bash
[ $# -ge 1 -a -f "$1" ] && input="$1" || input="-"
cat $input

ví dụ sau đó bạn có thể gọi kịch bản như

./myscript.sh filename

hoặc là

who | ./myscript.sh

Chỉnh sửa một số giải thích về kịch bản:

[ $# -ge 1 -a -f "$1" ]- Nếu ít nhất một đối số dòng lệnh ( $# -ge 1) AND (toán tử -a) thì đối số đầu tiên là một tệp (-f kiểm tra nếu "$ 1" là một tệp) thì kết quả kiểm tra là đúng.

&&là toán tử AND logic shell. Nếu kiểm tra là đúng, sau đó gán input="$1"cat $inputsẽ xuất tệp.

||là toán tử OR logic. Nếu kiểm tra là sai, thì các lệnh sau ||được phân tích cú pháp. đầu vào được gán cho "-". Lệnh cat -đọc từ bàn phím.

Tóm tắt, nếu đối số tập lệnh được cung cấp và nó là một tệp, thì đầu vào biến được gán cho tên tệp. Nếu không có đối số hợp lệ thì mèo đọc từ bàn phím.


&& input="$1" || input="-" làm gì và tại sao nó nằm ngoài testtoán tử?
cmo

Tôi đã thêm một chỉnh sửa với một số giải thích mà tôi hy vọng sẽ giúp.
suspectus

Điều gì xảy ra nếu tập lệnh có nhiều đối số ( $@)?
g33kz0r

12

readđọc từ đầu vào tiêu chuẩn. Chuyển hướng nó từ tệp ( ./script <someinput) hoặc qua ống ( dosomething | ./script) sẽ không làm cho nó hoạt động khác đi.

Tất cả những gì bạn phải làm là lặp qua tất cả các dòng trong đầu vào (và nó không khác với việc lặp qua các dòng trong tệp).

(mã mẫu, chỉ xử lý một dòng)

#!/bin/bash

read var
echo $var

Sẽ lặp lại dòng đầu tiên của đầu vào tiêu chuẩn của bạn (thông qua <hoặc |).


cảm ơn! Tôi chọn câu trả lời khác vì nó phù hợp với tôi hơn. tôi đã gói một đoạn script khác và tôi không muốn lặp lại cho đến khi tất cả đầu vào nhận được (có thể rất nhiều đầu vào ... sẽ lãng phí).
gilad hoch

4

Bạn không đề cập đến loại vỏ nào bạn dự định sử dụng, vì vậy tôi sẽ giả sử bash, mặc dù đây là những thứ khá chuẩn trên vỏ.

Đối số tệp

Các đối số có thể được truy cập thông qua các biến $1- $n( $0trả về lệnh được sử dụng để chạy chương trình). Giả sử tôi có một tập lệnh chỉ catxuất ra n số tệp với dấu phân cách giữa chúng:

#!/usr/bin/env bash
#
# Parameters:
#    1:   string delimiter between arguments 2-n
#    2-n: file(s) to cat out
for arg in ${@:2} # $@ is the array of arguments, ${@:2} slices it starting at 2.
do
   cat $arg
   echo $1
done

Trong trường hợp này, chúng tôi đang chuyển một tên tệp cho mèo. Tuy nhiên, nếu bạn muốn chuyển đổi dữ liệu trong tệp (không cần viết và viết lại rõ ràng), bạn cũng có thể lưu trữ nội dung tệp trong một biến:

file_contents=$(cat $filename)
[...do some stuff...]
echo $file_contents >> $new_filename

Đọc từ stdin

Theo như đọc từ stdin, hầu hết các shell đều có readnội dung khá chuẩn , mặc dù có những khác biệt về cách nhắc nhở được chỉ định (ít nhất là).

Trang người dùng Bash dựng sẵn có một lời giải thích khá súc tích read, nhưng tôi thích trang Bash Hackers hơn .

Đơn giản:

read var_name

Nhiều biến

Để đặt nhiều biến, chỉ cần cung cấp nhiều tên tham số cho read:

read var1 var2 var3

read sau đó sẽ đặt một từ từ stdin vào mỗi biến, bỏ tất cả các từ còn lại vào biến cuối cùng.

λ read var1 var2 var3
thing1 thing2 thing3 thing4 thing5
λ echo $var1; echo $var2; echo $var3
thing1
thing2
thing3 thing4 thing5

Nếu ít từ được nhập hơn biến, các biến còn lại sẽ trống (ngay cả khi được đặt trước đó):

λ read var1 var2 var3
thing1 thing2
λ echo $var1; echo $var2; echo $var3
thing1
thing2
# Empty line

Nhắc

Tôi sử dụng -pcờ thường xuyên cho một dấu nhắc:

read -p "Enter filename: " filename

Lưu ý: ZSH và KSH (và có lẽ những người khác) sử dụng một cú pháp khác nhau cho lời nhắc:

read "filename?Enter filename: " # Everything following the '?' is the prompt

Giá trị mặc định

Đây thực sự không phải là một readmẹo, nhưng tôi sử dụng nó rất nhiều kết hợp với read. Ví dụ:

read -p "Y/[N]: " reply
reply=${reply:-N}

Về cơ bản, nếu biến (trả lời) tồn tại, hãy trả về chính nó, nhưng nếu trống, hãy trả về tham số sau ("N").


4

Cách đơn giản nhất là tự chuyển hướng stdin:

if [ "$1" ] ; then exec < "$1" ; fi

Hoặc nếu bạn thích hình thức ngắn gọn hơn:

test "$1" && exec < "$1"

Bây giờ phần còn lại của kịch bản của bạn chỉ có thể đọc từ stdin. Tất nhiên bạn có thể làm tương tự với phân tích tùy chọn nâng cao hơn thay vì mã hóa cứng vị trí của tên tệp như "$1".


execsẽ cố gắng thực thi đối số dưới dạng một lệnh không phải là điều chúng ta muốn ở đây.
Suzana

@Suzana_K: Không phải khi nó không có đối số, như ở đây. Trong trường hợp đó, nó chỉ thay thế các mô tả tệp cho chính shell chứ không phải là một tiến trình con.
R ..

Tôi đã sao chép if [ "$1" ] ; then exec < "$1" ; fitrong một kịch bản thử nghiệm và nó đưa ra một thông báo lỗi vì lệnh này không được xác thực. Tương tự với hình thức terse.
Suzana

1
@Suzana_K: Bạn đang sử dụng cái vỏ nào? Nếu đó là sự thật thì đó không phải là một triển khai hoạt động của lệnh POSIX sh / Bourne shell.
R ..

GNU bash 4.3.11 trên Linux Mint Qiana
Suzana

3

sử dụng (hoặc loại bỏ) một cái gì đó khác đã hành xử theo cách này và sử dụng "$@"

giả sử tôi muốn viết một công cụ sẽ thay thế các khoảng trắng trong văn bản bằng các tab

trlà cách rõ ràng nhất để làm điều này, nhưng nó chỉ chấp nhận stdin, vì vậy chúng tôi phải loại bỏ cat:

$ cat entab1.sh
#!/bin/sh

cat "$@"|tr -s ' ' '\t'
$ cat entab1.sh|./entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ ./entab1.sh entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ 

đối với một ví dụ trong đó công cụ đang được sử dụng đã hoạt động theo cách này, sedthay vào đó chúng ta có thể thực hiện lại công cụ này :

$ cat entab2.sh
#!/bin/sh

sed -r 's/ +/\t/g' "$@"
$ 

3

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

#!/usr/bin/env bash

# Set variable input_file to either $1 or /dev/stdin, in case $1 is empty
# Note that this assumes that you are expecting the file name to operate on on $1
input_file="${1:-/dev/stdin}"

# You can now use "$input_file" as your file to operate on
cat "$input_file"

Để biết các thủ thuật thay thế tham số gọn gàng hơn trong Bash, xem điều này .


1
Cái này thật tuyệt! Tôi đang sử dụng uglifyjs < /dev/stdinvà nó hoạt động tuyệt vời!
fregante

0

Bạn cũng có thể giữ nó đơn giản và sử dụng mã này


Khi bạn tạo tệp tập lệnh pass_it_on.sh bằng mã này,

#!/bin/bash

cat

Bạn có thể chạy

cat SOMEFILE.txt | ./pass_it_on.sh

và tất cả nội dung của stdin sẽ được đưa ra màn hình.


Hoặc sử dụng mã này để cả hai giữ một bản sao của stdin trong một tệp và sau đó phun nó ra màn hình.

#!/bin/bash

tmpFile=`mktemp`
cat > $tmpFile
cat $tmpFile    

và đây là một ví dụ khác, có thể dễ đọc hơn, được giải thích ở đây:

http://mockingeye.com/blog/2013/01/22/reading-everything-stdin-in-a-bash-script/

#!/bin/bash

VALUE=$(cat)

echo "$VALUE"

Chúc vui vẻ.

RaamEE


0

Cách đơn giản nhất và tuân thủ POSIX là:

file=${1--}

tương đương với ${1:--}.

Sau đó đọc tệp như bình thường:

while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
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.