Câu trả lời:
Giải pháp sau đây đọc từ một tệp nếu tập lệnh được gọi với tên tệp là tham số đầu tiên $1
khác với đầu vào tiêu chuẩn.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
Việc thay thế ${1:-...}
sẽ được sử dụng $1
nếu được định nghĩa khác nếu tên tệp của đầu vào tiêu chuẩn của quy trình riêng được sử dụng.
/proc/$$/fd/0
và /dev/stdin
không? Tôi nhận thấy cái sau dường như phổ biến hơn và trông đơn giản hơn.
-r
vào read
lệnh của bạn , để nó không vô tình ăn \
chars; sử dụng while IFS= read -r line
để bảo tồn khoảng trắng hàng đầu và dấu.
/bin/sh
- bạn đang sử dụng một lớp vỏ ngoài bash
hay sh
?
Có lẽ giải pháp đơn giản nhất là chuyển hướng stdin với toán tử chuyển hướng hợp nhất:
#!/bin/bash
less <&0
Stdin là mô tả tập tin bằng không. Ở trên sẽ gửi đầu vào đường ống đến tập lệnh bash của bạn vào stdin ít hơn.
<&0
trong tình huống này - ví dụ của bạn sẽ hoạt động giống nhau hoặc không có nó - dường như, các công cụ bạn gọi từ trong tập lệnh bash theo mặc định sẽ xem stdin giống như chính tập lệnh (trừ khi tập lệnh sử dụng nó trước).
Đây là cách đơn giản nhất:
#!/bin/sh
cat -
Sử dụng:
$ echo test | sh my_script.sh
test
Để gán stdin cho biến, bạn có thể sử dụng: STDIN=$(cat -)
hoặc chỉ đơn giản STDIN=$(cat)
là toán tử là không cần thiết (theo nhận xét @ mkuity0 ).
Để phân tích từng dòng từ đầu vào tiêu chuẩn , hãy thử đoạn mã sau:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
Để đọc từ tệp hoặc stdin (nếu không có đối số), bạn có thể mở rộng nó thành:
#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
Ghi chú:
-
read -r
- Không xử lý ký tự dấu gạch chéo ngược theo bất kỳ cách đặc biệt nào. Xem xét mỗi dấu gạch chéo ngược là một phần của dòng đầu vào.- Không có cài đặt
IFS
, theo mặc định, các chuỗi Spacevà Tabở đầu và cuối của dòng bị bỏ qua (cắt xén).- Sử dụng
printf
thay vìecho
để tránh in các dòng trống khi dòng bao gồm một-e
,-n
hoặc-E
. Tuy nhiên, có một cách giải quyết bằng cách sử dụngenv POSIXLY_CORRECT=1 echo "$line"
mà thực thi GNU bên ngoài của bạnecho
hỗ trợ nó. Xem: Làm thế nào để tôi lặp lại "-e"?
Xem: Làm thế nào để đọc stdin khi không có đối số được thông qua? tại stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
để FILE=${1:--}
. (Quibble: tốt hơn để tránh các biến shell tất cả chữ hoa để tránh xung đột tên với các biến môi trường .)
${1:--}
là POSIX-compliant, vì vậy nó nên làm việc trong tất cả các POSIX giống như vỏ. Những gì sẽ không hoạt động trong tất cả các shell như vậy là quá trình thay thế ( <(...)
); chẳng hạn, nó sẽ hoạt động trong bash, ksh, zsh, nhưng không phải trong dấu gạch ngang. Ngoài ra, tốt hơn để thêm -r
vào read
lệnh của bạn , để nó không vô tình ăn \
chars; chuẩn bị IFS=
để bảo tồn khoảng trắng hàng đầu và dấu.
echo
: nếu một dòng bao gồm -e
, -n
hoặc -E
, nó sẽ không được hiển thị. Để khắc phục điều này, bạn phải sử dụng printf
: printf '%s\n' "$line"
. Tôi đã không đưa nó vào bản chỉnh sửa trước đây của mình quá thường xuyên, các chỉnh sửa của tôi bị đảo ngược khi tôi sửa lỗi này :(
.
--
sẽ vô ích nếu đối số đầu tiên là'%s\n'
IFS=
cùng read
và printf
thay vì echo
. :)
.
Tôi nghĩ rằng đây là cách đơn giản:
$ cat reader.sh
#!/bin/bash
while read line; do
echo "reading: ${line}"
done < /dev/stdin
-
$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
echo "line ${i}"
done
-
$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5
read
đọc từ stdin theo mặc định , do đó không cần phải cho < /dev/stdin
.
Các echo
giải pháp bổ sung thêm dòng mới bất cứ khi nào IFS
phá vỡ các dòng đầu vào. Câu trả lời của @ fgm có thể được sửa đổi một chút:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
hành vi của: trong khi đó read
có khả năng phân chia thành nhiều mã thông báo bằng ký tự. chứa trong $IFS
nó, nó chỉ trả về một mã thông báo duy nhất nếu bạn chỉ chỉ định một tên biến duy nhất (nhưng cắt và khoảng trắng theo dõi và theo dõi theo mặc định).
read
và $IFS
- echo
chính nó thêm các dòng mới mà không cần -n
cờ. "Tiện ích echo ghi bất kỳ toán hạng được chỉ định nào, được phân tách bằng các ký tự trống (` ') và theo sau là một ký tự dòng mới (`\ n'), cho đầu ra tiêu chuẩn."
\n
được thêm bởi echo
: Perl's $_
bao gồm dòng kết thúc \n
từ dòng đọc, trong khi bash read
thì không. (Tuy nhiên, như @gniourf_gniourf chỉ ra ở nơi khác, cách tiếp cận mạnh mẽ hơn là sử dụng printf '%s\n'
thay cho echo
).
Vòng lặp Perl trong câu hỏi đọc từ tất cả các đối số tên tệp trên dòng lệnh hoặc từ đầu vào tiêu chuẩn nếu không có tệp nào được chỉ định. Các câu trả lời tôi thấy dường như tất cả đều xử lý một tệp hoặc đầu vào tiêu chuẩn nếu không có tệp nào được chỉ định.
Mặc dù thường bị chế giễu chính xác là UUOC (Sử dụng vô dụng cat
), đôi khi cat
có công cụ tốt nhất cho công việc và người ta cho rằng đây là một trong số đó:
cat "$@" |
while read -r line
do
echo "$line"
done
Nhược điểm duy nhất của việc này là nó tạo ra một đường ống chạy trong lớp vỏ phụ, do đó, những thứ như phép gán biến trong while
vòng lặp không thể truy cập được bên ngoài đường ống. Các bash
con đường xung quanh đó là Process Thay :
while read -r line
do
echo "$line"
done < <(cat "$@")
Điều này làm cho while
vòng lặp chạy trong vỏ chính, vì vậy các biến được đặt trong vòng lặp có thể truy cập bên ngoài vòng lặp.
>>EOF\n$(cat "$@")\nEOF
. Cuối cùng, một ngụy biện: while IFS= read -r line
là một xấp xỉ tốt hơn những gì while (<>)
làm trong Perl (duy trì khoảng trắng hàng đầu và dấu vết - mặc dù Perl cũng giữ dấu vết \n
).
Hành vi của Perl, với mã được đưa ra trong OP có thể không có hoặc có một vài đối số và nếu một đối số là một dấu gạch nối đơn -
thì điều này được hiểu là stdin. Hơn nữa, luôn luôn có thể có tên tệp với $ARGV
. Không có câu trả lời nào được đưa ra cho đến nay thực sự bắt chước hành vi của Perl ở những khía cạnh này. Đây là một khả năng Bash thuần túy. Bí quyết là sử dụng exec
hợp lý.
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
Tên tệp có sẵn trong $1
.
Nếu không có đối số nào được đưa ra, chúng ta đặt giả tạo -
là tham số vị trí đầu tiên. Chúng tôi sau đó lặp trên các tham số. Nếu một tham số là không -
, chúng tôi chuyển hướng đầu vào tiêu chuẩn từ tên tệp với exec
. Nếu chuyển hướng này thành công, chúng ta lặp với một while
vòng lặp. Tôi đang sử dụng REPLY
biến tiêu chuẩn và trong trường hợp này bạn không cần đặt lại IFS
. Nếu bạn muốn một tên khác, bạn phải đặt lại IFS
như vậy (tất nhiên trừ khi bạn không muốn điều đó và biết bạn đang làm gì):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Chính xác hơn...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
và -r
vào read
lệnh đảm bảo rằng mỗi dòng được đọc không thay đổi (bao gồm cả khoảng trắng đầu và cuối).
Vui lòng thử mã sau đây:
while IFS= read -r line; do
echo "$line"
done < file
read
không IFS=
và -r
, và người nghèo $line
mà không có dấu ngoặc kép khỏe mạnh của nó.
read -r
ký hiệu này. IMO, POSIX đã sai; tùy chọn sẽ kích hoạt ý nghĩa đặc biệt cho dấu gạch chéo ngược, không vô hiệu hóa nó - để các tập lệnh hiện có (từ trước khi POSIX tồn tại) sẽ không bị hỏng vì -r
đã bị bỏ qua. Tuy nhiên, tôi quan sát rằng đó là một phần của IEEE 1003.2 1992, đây là phiên bản đầu tiên của vỏ POSIX và tiêu chuẩn tiện ích, nhưng nó được đánh dấu là một bổ sung ngay cả khi đó, vì vậy điều này đang làm hỏng các cơ hội lâu dài. Tôi chưa bao giờ gặp rắc rối vì mã của tôi không sử dụng -r
; Tôi phải may mắn. Mặc kệ tôi về điều này.
-r
phải là tiêu chuẩn. Tôi đồng ý rằng không có khả năng xảy ra trường hợp không sử dụng nó dẫn đến rắc rối. Mặc dù, mã bị hỏng là mã bị hỏng. Chỉnh sửa của tôi lần đầu tiên được kích hoạt bởi $line
biến nghèo nàn đó đã bỏ lỡ các trích dẫn của nó. Tôi đã sửa read
trong khi tôi đang ở đó. Tôi đã không sửa lỗi echo
vì đó là loại chỉnh sửa được khôi phục. :(
.
Mã ${1:-/dev/stdin}
sẽ chỉ hiểu đối số đầu tiên, vì vậy, làm thế nào về điều này.
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
Tôi không tìm thấy bất kỳ câu trả lời nào trong số này. Cụ thể, câu trả lời được chấp nhận chỉ xử lý tham số dòng lệnh đầu tiên và bỏ qua phần còn lại. Chương trình Perl mà nó đang cố gắng mô phỏng xử lý tất cả các tham số dòng lệnh. Vì vậy, câu trả lời được chấp nhận thậm chí không trả lời câu hỏi. Các câu trả lời khác sử dụng tiện ích mở rộng bash, thêm các lệnh 'cat' không cần thiết, chỉ hoạt động trong trường hợp đơn giản là lặp lại đầu vào thành đầu ra hoặc chỉ phức tạp không cần thiết.
Tuy nhiên, tôi phải cung cấp cho họ một số tín dụng vì họ đã cho tôi một số ý tưởng. Đây là câu trả lời đầy đủ:
#!/bin/sh
if [ $# = 0 ]
then
DEFAULT_INPUT_FILE=/dev/stdin
else
DEFAULT_INPUT_FILE=
fi
# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
while IFS= read -r LINE
do
# Do whatever you want with LINE here.
echo $LINE
done < "$FILE"
done
Tôi đã kết hợp tất cả các câu trả lời ở trên và tạo ra một hàm shell phù hợp với nhu cầu của tôi. Đây là từ một thiết bị đầu cuối cygwin của 2 máy Windows10 của tôi, nơi tôi có một thư mục dùng chung giữa chúng. Tôi cần có khả năng xử lý như sau:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
Khi một tên tệp cụ thể được chỉ định, tôi cần sử dụng cùng tên tệp trong khi sao chép. Trường hợp luồng dữ liệu đầu vào đã được chuyển qua, thì tôi cần tạo một tên tệp tạm thời có giờ và giây. Mainfolder được chia sẻ có các thư mục con của các ngày trong tuần. Đây là cho mục đích tổ chức.
Kìa, kịch bản cuối cùng cho nhu cầu của tôi:
tx ()
{
if [ $# -eq 0 ]; then
local TMP=/tmp/tx.$(date +'%H%M%S')
while IFS= read -r line; do
echo "$line"
done < /dev/stdin > $TMP
cp $TMP //$OTHER/stargate/$(date +'%a')/
rm -f $TMP
else
[ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
fi
}
Nếu có bất kỳ cách nào bạn có thể thấy để tiếp tục tối ưu hóa điều này, tôi muốn biết.
Phần sau hoạt động với tiêu chuẩn sh
(Đã thử nghiệm dash
trên Debian) và khá dễ đọc, nhưng đó là vấn đề của hương vị:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Chi tiết: Nếu tham số đầu tiên không trống thì cat
tệp đó, cat
đầu vào tiêu chuẩn khác . Sau đó, đầu ra của toàn bộ if
câu lệnh được xử lý bởi commands_and_transformations
.
cat "${1:--}" | any_command
. Đọc các biến shell và lặp lại chúng có thể hoạt động đối với các tệp nhỏ nhưng không mở rộng tốt như vậy.
[ -n "$1" ]
thể được đơn giản hóa để [ "$1" ]
.
Làm thế nào về
for line in `cat`; do
something($line);
done
cat
sẽ được đặt vào dòng lệnh. Dòng lệnh có kích thước tối đa. Ngoài ra, điều này sẽ không đọc từng dòng, nhưng từng chữ.