Jawaban:
Solusi berikut dibaca dari file jika skrip dipanggil dengan nama file sebagai parameter pertama $1
dari input standar.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
Substitusi ${1:-...}
mengambil $1
jika didefinisikan sebaliknya nama file dari input standar dari proses sendiri digunakan.
/proc/$$/fd/0
dan /dev/stdin
? Saya perhatikan yang terakhir tampaknya lebih umum dan terlihat lebih mudah.
-r
Anda read
, sehingga tidak sengaja memakan \
karakter; gunakan while IFS= read -r line
untuk melestarikan spasi putih terkemuka dan tertinggal.
/bin/sh
- apakah Anda menggunakan shell selain bash
atau sh
?
Mungkin solusi paling sederhana adalah mengarahkan ulang stdin dengan operator pengalihan penggabungan:
#!/bin/bash
less <&0
Stdin adalah deskriptor file nol. Di atas mengirimkan input yang disalurkan ke skrip bash Anda ke less's stdin.
<&0
dalam situasi ini - contoh Anda akan bekerja sama dengan atau tanpa itu - tampaknya, alat yang Anda panggil dari dalam skrip bash secara default melihat stdin yang sama dengan skrip itu sendiri (kecuali skrip yang menggunakannya terlebih dahulu).
Inilah cara paling sederhana:
#!/bin/sh
cat -
Pemakaian:
$ echo test | sh my_script.sh
test
Untuk menetapkan stdin ke variabel, Anda dapat menggunakan: STDIN=$(cat -)
atau hanya STDIN=$(cat)
karena operator tidak diperlukan (sesuai komentar @ mklement0 ).
Untuk mengurai setiap baris dari input standar , coba skrip berikut:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
Untuk membaca dari file atau stdin (jika argumen tidak ada), Anda dapat memperluas ke:
#!/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")
Catatan:
-
read -r
- Jangan perlakukan karakter backslash dengan cara khusus apa pun. Pertimbangkan setiap garis miring terbalik sebagai bagian dari jalur input.- Tanpa pengaturan
IFS
, secara default urutan Spacedan Tabdi awal dan akhir garis diabaikan (dipangkas).- Gunakan
printf
alih-alihecho
untuk menghindari mencetak garis kosong ketika garis terdiri dari satu-e
,-n
atau-E
. Namun ada solusi dengan menggunakanenv POSIXLY_CORRECT=1 echo "$line"
yang mengeksekusi GNU eksternal Andaecho
yang mendukungnya. Lihat: Bagaimana saya menggemakan "-e"?
Lihat: Bagaimana membaca stdin ketika tidak ada argumen yang disampaikan? di stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
untuk FILE=${1:--}
. (Quibble: lebih baik untuk menghindari variabel shell huruf besar semua untuk menghindari tabrakan nama dengan variabel lingkungan .)
${1:--}
adalah POSIX-compliant, jadi ia harus bekerja di semua kerang mirip POSIX. Apa yang tidak akan bekerja di semua shell tersebut adalah substitusi proses ( <(...)
); itu akan bekerja di bash, ksh, zsh, tetapi tidak di dash, misalnya. Juga, lebih baik menambahkan perintah -r
Anda read
, sehingga tidak sengaja memakan \
karakter; bertanggung jawab IFS=
untuk melestarikan spasi putih terkemuka dan tertinggal.
echo
: jika suatu baris terdiri dari -e
, -n
atau -E
, itu tidak akan ditampilkan. Untuk mengatasinya, Anda harus menggunakan printf
: printf '%s\n' "$line"
. Saya tidak memasukkannya dalam edit saya sebelumnya ... terlalu sering suntingan saya dibatalkan ketika saya memperbaiki kesalahan ini :(
.
--
tidak ada gunanya jika argumen pertama adalah'%s\n'
IFS=
dengan read
dan printf
bukan echo
. :)
.
Saya pikir ini adalah jalan lurus ke depan:
$ 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
membaca dari stdin secara default , jadi ada tidak perlu untuk < /dev/stdin
.
The echo
solusi menambahkan baris baru setiap kali IFS
istirahat input stream. Jawaban @ fgm dapat dimodifikasi sedikit:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
's perilaku: sementara read
tidak berpotensi dibagi menjadi beberapa token oleh karakter. terkandung dalam $IFS
, itu hanya mengembalikan token tunggal jika Anda hanya menentukan nama variabel tunggal (tetapi trims dan memimpin dan mengikuti spasi putih secara default).
read
dan $IFS
- echo
itu sendiri menambahkan baris baru tanpa -n
bendera. "Utilitas gema menulis operan tertentu, dipisahkan oleh karakter tunggal kosong (` ') dan diikuti oleh karakter baris baru (`\ n'), ke output standar."
\n
ditambahkan oleh echo
: Perl $_
termasuk baris yang berakhir \n
dari baris yang dibaca, sedangkan bash read
tidak. (Namun, seperti yang ditunjukkan @gniourf_gniourf di tempat lain, pendekatan yang lebih kuat adalah untuk digunakan printf '%s\n'
sebagai pengganti echo
).
Loop Perl dalam pertanyaan membaca dari semua argumen nama file pada baris perintah, atau dari input standar jika tidak ada file yang ditentukan. Jawaban yang saya lihat sepertinya memproses satu file atau input standar jika tidak ada file yang ditentukan.
Meskipun sering diejek secara akurat sebagai UUOC (Penggunaan yang Tidak Berguna cat
), ada kalanya cat
alat terbaik untuk pekerjaan itu, dan dapat diperdebatkan bahwa ini adalah salah satunya:
cat "$@" |
while read -r line
do
echo "$line"
done
Satu-satunya downside ke ini adalah bahwa ia menciptakan sebuah pipa berjalan di sub-shell, sehingga hal-hal seperti penugasan variabel dalam while
loop tidak dapat diakses di luar pipa. The bash
cara mengatasinya adalah Proses Pergantian :
while read -r line
do
echo "$line"
done < <(cat "$@")
Ini meninggalkan while
loop berjalan di shell utama, sehingga variabel yang diatur dalam loop dapat diakses di luar loop.
>>EOF\n$(cat "$@")\nEOF
. Akhirnya, quibble: while IFS= read -r line
adalah perkiraan yang lebih baik dari apa yang while (<>)
dilakukan di Perl (mempertahankan spasi putih terdepan dan tertinggal - meskipun Perl juga menjaga trailing \n
).
Perilaku Perl, dengan kode yang diberikan dalam OP dapat mengambil tidak ada atau beberapa argumen, dan jika argumen adalah tanda hubung tunggal -
ini dipahami sebagai stdin. Selain itu, selalu memungkinkan untuk memiliki nama file $ARGV
. Tidak ada jawaban yang diberikan sejauh ini yang benar-benar meniru perilaku Perl dalam hal ini. Inilah kemungkinan Bash murni. Caranya adalah menggunakan dengan exec
tepat.
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
Nama file tersedia di $1
.
Jika tidak ada argumen yang diberikan, kami secara artifisial menetapkan -
sebagai parameter posisi pertama. Kami kemudian mengulangi parameter. Jika parameter tidak -
, kami mengarahkan input standar dari nama file dengan exec
. Jika pengalihan ini berhasil, kami mengulang dengan while
loop. Saya menggunakan REPLY
variabel standar , dan dalam hal ini Anda tidak perlu mengatur ulang IFS
. Jika Anda ingin nama lain, Anda harus mengatur ulang IFS
seperti itu (kecuali, tentu saja, Anda tidak menginginkannya dan tahu apa yang Anda lakukan):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Lebih akurat...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
dan -r
ke read
perintah memastikan bahwa setiap baris dibaca tidak dimodifikasi (termasuk spasi spasi awal dan akhir).
Silakan coba kode berikut:
while IFS= read -r line; do
echo "$line"
done < file
read
tanpa IFS=
dan -r
, dan orang miskin $line
tanpa tanda kutip yang sehat.
read -r
notasi. IMO, POSIX salah; opsi harus mengaktifkan makna khusus untuk trailing backslash, bukan menonaktifkannya - sehingga skrip yang ada (dari sebelum POSIX ada) tidak akan rusak karena -r
dihilangkan. Saya mengamati, bagaimanapun, bahwa itu adalah bagian dari IEEE 1003.2 1992, yang merupakan versi paling awal dari shell POSIX dan standar utilitas, tetapi itu ditandai sebagai tambahan bahkan kemudian, jadi ini menggerutu tentang peluang yang sudah lama hilang. Saya tidak pernah mengalami masalah karena kode saya tidak digunakan -r
; Saya pasti beruntung. Abaikan aku dalam hal ini.
-r
harus menjadi standar. Saya setuju bahwa tidak mungkin dalam kasus di mana tidak menggunakannya menyebabkan masalah. Padahal, kode rusak adalah kode rusak. Suntingan saya pertama-tama dipicu oleh $line
variabel yang buruk itu yang terlewatkan kutipnya. Saya memperbaiki read
sementara saya berada di itu. Saya tidak memperbaiki echo
karena itulah jenis pengeditan yang dibatalkan. :(
.
Kode ${1:-/dev/stdin}
hanya akan mengerti argumen pertama, jadi, bagaimana dengan ini.
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
Saya tidak menemukan jawaban ini yang dapat diterima. Secara khusus, jawaban yang diterima hanya menangani parameter baris perintah pertama dan mengabaikan sisanya. Program Perl yang ia coba tiru menangani semua parameter baris perintah. Jadi jawaban yang diterima bahkan tidak menjawab pertanyaan. Jawaban lain menggunakan ekstensi bash, tambahkan perintah 'cat' yang tidak perlu, hanya berfungsi untuk kasus sederhana dari gema input ke output, atau hanya rumit yang tidak perlu.
Namun, saya harus memberi mereka kredit karena mereka memberi saya beberapa ide. Inilah jawaban lengkapnya:
#!/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
Saya menggabungkan semua jawaban di atas dan membuat fungsi shell yang sesuai dengan kebutuhan saya. Ini dari terminal cygwin dari 2 mesin Windows10 saya di mana saya memiliki folder bersama di antara mereka. Saya harus bisa menangani yang berikut ini:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
Di mana nama file tertentu ditentukan, saya harus menggunakan nama file yang sama saat menyalin. Di mana aliran data input telah disalurkan melalui, maka saya perlu membuat nama file sementara yang memiliki jam menit dan detik. Mainfolder bersama memiliki subfolder dari hari-hari dalam seminggu. Ini untuk tujuan organisasi.
Lihatlah, naskah akhir untuk kebutuhan saya:
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
}
Jika ada cara yang dapat Anda lihat untuk lebih mengoptimalkan ini, saya ingin tahu.
Berikut ini berfungsi dengan standar sh
(Diuji dengan dash
pada Debian) dan cukup mudah dibaca, tapi itu masalah selera:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Detail: Jika parameter pertama tidak kosong maka cat
file itu, atau cat
input standar. Kemudian output dari seluruh if
pernyataan diproses oleh commands_and_transformations
.
cat "${1:--}" | any_command
. Membaca ke variabel shell dan menggema mereka mungkin bekerja untuk file kecil tetapi tidak skala dengan baik.
[ -n "$1" ]
dapat disederhanakan [ "$1" ]
.
Bagaimana tentang
for line in `cat`; do
something($line);
done
cat
akan ditempatkan ke dalam baris perintah. Baris perintah memiliki ukuran maksimum. Juga ini tidak akan membaca baris demi baris, tetapi kata demi kata.