Bagaimana cara menulis skrip yang menerima input dari file atau dari stdin?

57

Bagaimana seseorang dapat menulis skrip yang menerima input dari argumen nama file atau dari stdin?

misalnya, Anda bisa menggunakan lesscara ini. seseorang dapat mengeksekusi less filenamedan setara cat filename | less.

apakah ada cara "out of the box" yang mudah untuk melakukannya? atau apakah saya perlu menemukan kembali roda dan menulis sedikit logika dalam skrip?

gilad hoch
sumber
@PlasmaPower Selama pertanyaannya adalah pada topik pada SU, tidak ada persyaratan untuk bertanya di situs SE yang berbeda. Banyak situs SE yang tumpang tindih; umumnya kita tidak perlu menyarankan situs yang tumpang tindih kecuali jika pertanyaannya adalah di luar topik (dalam hal ini, suara untuk bermigrasi) atau sesuai topik tetapi tidak mendapatkan banyak respons (dalam hal ini, penanya harus menandai untuk moderator- perhatian / migrasi, bukan lintas pos).
Bob

Jawaban:

59

Jika argumen file adalah argumen pertama untuk skrip Anda, uji bahwa ada argumen ( $1) dan itu adalah file. Lain membaca input dari stdin -

Jadi skrip Anda dapat berisi sesuatu seperti ini:

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

mis. maka Anda dapat memanggil skrip seperti

./myscript.sh filename

atau

who | ./myscript.sh

Edit beberapa penjelasan skrip:

[ $# -ge 1 -a -f "$1" ]- Jika setidaknya satu argumen baris perintah ( $# -ge 1) DAN (-a operator) argumen pertama adalah file (-f menguji jika "$ 1" adalah file) maka hasil tes benar.

&&adalah shell AND operator. Jika pengujian benar, maka tetapkan input="$1"dan cat $inputakan output file.

||adalah shell OR operator. Jika tes ini salah, maka perintah berikut ||diuraikan. input ditugaskan ke "-". Perintah cat -membaca dari keyboard.

Meringkas, jika argumen skrip disediakan dan itu adalah file, maka input variabel ditugaskan ke nama file. Jika tidak ada argumen yang valid maka kucing membaca dari keyboard.

tersangka
sumber
apa yang && input="$1" || input="-" dilakukan dan mengapa itu di luar testoperator?
cmo
Saya telah menambahkan suntingan dengan beberapa penjelasan yang saya harap akan membantu.
tersangka
Bagaimana jika skrip memiliki beberapa argumen ( $@)?
g33kz0r
12

readmembaca dari input standar. Mengarahkannya dari file ( ./script <someinput) atau melalui pipe ( dosomething | ./script) tidak akan membuatnya bekerja secara berbeda.

Yang harus Anda lakukan adalah mengulangi semua baris dalam input (dan itu tidak berbeda dari iterasi pada baris dalam file).

(kode sampel, hanya memproses satu baris)

#!/bin/bash

read var
echo $var

Akan menggemakan baris pertama input standar Anda (baik melalui <atau |).

Lukasz Daniluk
sumber
Terima kasih! saya memilih jawaban yang lain karena lebih cocok untuk saya. Saya sedang membungkus skrip lain, dan saya tidak ingin mengulang sampai semua input diterima (bisa jadi banyak input ... akan boros).
gilad hoch
4

Anda tidak menyebutkan shell apa yang Anda rencanakan untuk digunakan, jadi saya akan menganggap bash, meskipun ini adalah hal-hal yang cukup standar di seluruh shell.

File Argumen

Argumen dapat diakses melalui variabel $1- $n( $0mengembalikan perintah yang digunakan untuk menjalankan program). Katakanlah saya memiliki skrip yang baru saja catkeluar n jumlah file dengan pembatas di antaranya:

#!/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

Dalam hal ini, kami memberikan nama file ke cat. Namun, jika Anda ingin mengubah data dalam file (tanpa menulis dan menulis ulang secara eksplisit), Anda juga bisa menyimpan konten file dalam variabel:

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

Baca dari stdin

Sejauh membaca dari stdin, kebanyakan shell memiliki readbuiltin yang cukup standar , meskipun ada perbedaan dalam cara prompt ditentukan (paling tidak).

The Bash Halaman builtin pria memiliki penjelasan singkat cantik read, tapi aku lebih suka Bash Hacker halaman.

Secara sederhana:

read var_name

Beberapa variabel

Untuk mengatur beberapa variabel, cukup berikan beberapa nama parameter ke read:

read var1 var2 var3

read kemudian akan menempatkan satu kata dari stdin ke setiap variabel, membuang semua kata yang tersisa ke variabel terakhir.

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

Jika lebih sedikit kata yang dimasukkan daripada variabel, variabel sisa akan kosong (bahkan jika ditetapkan sebelumnya):

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

Anjuran

Saya -psering menggunakan flag untuk prompt:

read -p "Enter filename: " filename

Catatan: ZSH dan KSH (dan mungkin yang lain) menggunakan sintaks yang berbeda untuk prompt:

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

Nilai dasar

Ini sebenarnya bukan readtipuan, tapi saya sering menggunakannya bersama read. Sebagai contoh:

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

Pada dasarnya, jika variabel (balasan) ada, kembalikan sendiri, tetapi jika kosong, kembalikan parameter berikut ("N").

Jacob Hayes
sumber
4

Cara paling sederhana adalah dengan mengarahkan ulang stdin sendiri:

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

Atau jika Anda lebih suka bentuk yang lebih singkat:

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

Sekarang sisa skrip Anda dapat dibaca dari stdin. Tentu saja Anda dapat melakukan hal yang sama dengan parsing opsi lebih maju daripada mengkodekan posisi nama file sebagai "$1".

R ..
sumber
execakan mencoba menjalankan argumen sebagai perintah yang tidak kita inginkan di sini.
Suzana
@ Suzana_K: Tidak ketika tidak memiliki argumen, seperti di sini. Dalam hal itu hanya menggantikan deskriptor file untuk shell itu sendiri daripada proses anak.
R ..
Saya menyalin if [ "$1" ] ; then exec < "$1" ; fidalam skrip pengujian dan memberikan pesan kesalahan karena perintahnya tidak dikenal. Sama dengan bentuk singkat.
Suzana
1
@ Suzana_K: Shell apa yang Anda gunakan? Jika itu benar, itu bukan implementasi perintah POSIX sh / Bourne shell.
R ..
GNU bash 4.3.11 di Linux Mint Qiana
Suzana
3

gunakan (atau matikan) sesuatu yang lain yang sudah berperilaku seperti ini, dan gunakan "$@"

katakanlah saya ingin menulis alat yang akan menggantikan run spasi dalam teks dengan tab

tradalah cara yang paling jelas untuk melakukan ini, tetapi hanya menerima stdin, jadi kita harus melepaskan 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'
$ 

untuk contoh di mana alat yang digunakan sudah berperilaku seperti ini, kita bisa menerapkannya dengan sed:

$ cat entab2.sh
#!/bin/sh

sed -r 's/ +/\t/g' "$@"
$ 
Aaron Davies
sumber
3

Anda juga dapat melakukan:

#!/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"

Untuk trik substitusi parameter yang lebih rapi di Bash, lihat ini .

Daniel
sumber
1
Ini fantastis! Saya menggunakan uglifyjs < /dev/stdindan bekerja sangat baik!
fregante
0

Anda juga dapat membuatnya tetap sederhana dan menggunakan kode ini


Saat Anda membuat file skrip pass_it_on.sh dengan kode ini,

#!/bin/bash

cat

Anda bisa lari

cat SOMEFILE.txt | ./pass_it_on.sh

dan semua isi stdin hanya akan dimuntahkan ke layar.


Atau gunakan kode ini untuk menyimpan salinan stdin dalam sebuah file dan kemudian memuntahkannya ke layar.

#!/bin/bash

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

dan di sini adalah contoh lain, mungkin lebih mudah dibaca, dijelaskan di sini:

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

#!/bin/bash

VALUE=$(cat)

echo "$VALUE"

Selamat bersenang-senang.

RaamEE

RaamEE
sumber
0

Cara paling sederhana dan patuh POSIX adalah:

file=${1--}

yang setara dengan ${1:--}.

Kemudian baca file seperti biasa:

while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
kenorb
sumber