จะเขียนสคริปต์ที่รับอินพุตจากไฟล์หรือจาก stdin ได้อย่างไร?


57

เราจะเขียนสคริปต์ที่ยอมรับอินพุตจากอาร์กิวเมนต์ชื่อไฟล์หรือจาก stdin ได้อย่างไร

ตัวอย่างเช่นคุณสามารถใช้lessวิธีนี้ หนึ่งสามารถดำเนินการและค่าเท่ากันless filenamecat filename | less

มีวิธี "นอกกรอบ" ง่าย ๆ ในการทำเช่นนั้น? หรือฉันจำเป็นต้องคิดค้นวงล้อใหม่และเขียนตรรกะเล็กน้อยในสคริปต์หรือไม่


@PlasmaPower ตราบใดที่คำถามเป็นหัวข้อของ SU ไม่จำเป็นต้องถามไซต์ SE อื่น ไซต์ SE จำนวนมากมีการทับซ้อนกัน โดยทั่วไปเราไม่จำเป็นต้องแนะนำไซต์ที่ทับซ้อนกันเว้นแต่ว่าคำถามนั้นเป็นแบบนอกหัวข้อ (ในกรณีนี้ให้ลงคะแนนเพื่อโยกย้าย) หรือในหัวข้อ แต่ไม่ได้รับการตอบสนองมากนัก (ในกรณีนี้ผู้ถามควรตั้งค่าสถานะสำหรับผู้ดูแล ความสนใจ / การโยกย้ายไม่ใช่การข้ามโพสต์)
Bob

คำตอบ:


59

หากอาร์กิวเมนต์ file เป็นอาร์กิวเมนต์แรกของสคริปต์ของคุณให้ทดสอบว่ามีอาร์กิวเมนต์ ( $1) และเป็นไฟล์ อื่น ๆ อ่านอินพุตจาก stdin -

ดังนั้นสคริปต์ของคุณอาจมีสิ่งนี้:

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

เช่นจากนั้นคุณสามารถเรียกใช้สคริปต์เช่น

./myscript.sh filename

หรือ

who | ./myscript.sh

แก้ไข คำอธิบายบางส่วนของสคริปต์:

[ $# -ge 1 -a -f "$1" ]- หากอาร์กิวเมนต์บรรทัดคำสั่งอย่างน้อยหนึ่งรายการ ( $# -ge 1) และ (ตัวดำเนินการ -a) อาร์กิวเมนต์แรกคือไฟล์ (-f ทดสอบว่า "$ 1" เป็นไฟล์) ดังนั้นผลการทดสอบจะเป็นจริง

&&เป็นตัวดำเนินการเชลล์ตรรกะและ หากการทดสอบเป็นจริงจากนั้นกำหนดinput="$1"และcat $inputจะส่งออกไฟล์

||เป็นตัวดำเนินการ OR แบบลอจิคัลของเชลล์ หากการทดสอบเป็นเท็จคำสั่งต่อไปนี้||จะถูกแยกวิเคราะห์ อินพุตถูกกำหนดให้กับ "-" คำสั่งcat -อ่านจากแป้นพิมพ์

โดยสรุปหากมีการจัดเตรียมอาร์กิวเมนต์ของสคริปต์และเป็นไฟล์การป้อนตัวแปรจะถูกกำหนดให้กับชื่อไฟล์ หากไม่มีข้อโต้แย้งที่ถูกต้องแล้ว cat อ่านจากแป้นพิมพ์


สิ่งที่ไม่ && input="$1" || input="-" ทำและทำไมมันอยู่นอกtestผู้ประกอบการ?
cmo

ฉันได้เพิ่มการแก้ไขพร้อมคำอธิบายซึ่งฉันหวังว่าจะช่วยได้
สงสัย

จะเกิดอะไรขึ้นถ้าสคริปต์มีหลายอาร์กิวเมนต์ ( $@)
g33kz0r

12

readอ่านจากอินพุตมาตรฐาน การเปลี่ยนเส้นทางจากไฟล์ ( ./script <someinput) หรือผ่านไปป์ ( dosomething | ./script) จะไม่ทำให้การทำงานแตกต่างกัน

สิ่งที่คุณต้องทำคือการวนซ้ำทุกบรรทัดในอินพุต (และมันก็ไม่ได้แตกต่างจากการวนซ้ำไปตามบรรทัดในไฟล์)

(โค้ดตัวอย่างประมวลผลเพียงหนึ่งบรรทัด)

#!/bin/bash

read var
echo $var

จะสะท้อนบรรทัดแรกของอินพุตมาตรฐานของคุณ (ผ่าน<หรือ|)


ขอบคุณ! ฉันเลือกคำตอบอื่นเพราะมันเหมาะกับฉันดีกว่า ฉันกำลังเขียนสคริปต์อื่นและฉันไม่ต้องการวนซ้ำจนกว่าอินพุตทั้งหมดจะได้รับ (อาจเป็นอินพุตจำนวนมาก ... จะสิ้นเปลือง)
gilad hoch

4

คุณไม่ได้พูดถึงว่าคุณวางแผนที่จะใช้เชลล์อย่างไรดังนั้นฉันจะถือว่าทุบตีแม้ว่าสิ่งเหล่านี้จะเป็นมาตรฐานธรรมดาในทุกเชลล์

อาร์กิวเมนต์ไฟล์

อาร์กิวเมนต์สามารถเข้าถึงได้ผ่านตัวแปร$1- $n( $0ส่งคืนคำสั่งที่ใช้เพื่อรันโปรแกรม) ว่าฉันมีสคริปต์ที่เพิ่งcatออกจำนวนไฟล์ที่มีตัวคั่นระหว่างพวกเขา:

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

ในกรณีนี้เรากำลังส่งชื่อไฟล์ให้ cat อย่างไรก็ตามหากคุณต้องการแปลงข้อมูลในไฟล์ (โดยไม่ต้องเขียนและเขียนใหม่อย่างชัดเจน) คุณสามารถจัดเก็บเนื้อหาไฟล์ในตัวแปร:

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

อ่านจาก stdin

เท่าที่อ่านจาก stdin เชลล์ส่วนใหญ่มีreadbuiltin มาตรฐานที่สวยงามแม้ว่าจะมีความแตกต่างในวิธีการระบุพรอมต์ (อย่างน้อยที่สุด)

ทุบตี builtins หน้าคนมีคำอธิบายที่กระชับสวยreadแต่ฉันชอบทุบตีแฮกเกอร์หน้า

เพียง:

read var_name

หลายตัวแปร

หากต้องการตั้งค่าตัวแปรหลายตัวเพียงระบุชื่อพารามิเตอร์หลายตัวเป็นread:

read var1 var2 var3

read จากนั้นจะวางคำหนึ่งคำจาก stdin ลงในแต่ละตัวแปรทิ้งคำที่เหลือทั้งหมดลงในตัวแปรสุดท้าย

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

หากป้อนคำน้อยกว่าตัวแปรตัวแปรที่เหลือจะว่างเปล่า (แม้ว่าจะตั้งไว้ก่อนหน้านี้):

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

แจ้ง

ฉันใช้การ-pตั้งค่าสถานะบ่อยครั้งเพื่อแจ้งให้:

read -p "Enter filename: " filename

หมายเหตุ: ZSH และ KSH (และอื่น ๆ ) ใช้ไวยากรณ์ที่แตกต่างกันสำหรับการแจ้งเตือน:

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

ค่าเริ่มต้น

นี้ไม่ได้จริงๆreadเคล็ดลับ readแต่ฉันจะใช้มันมากร่วมกับ ตัวอย่างเช่น:

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

โดยทั่วไปหากตัวแปร (ตอบกลับ) มีอยู่ให้ส่งคืนตัวเอง แต่ถ้าว่างเปล่าให้ส่งคืนพารามิเตอร์ต่อไปนี้ ("N")


4

วิธีที่ง่ายที่สุดคือเปลี่ยนเส้นทาง stdin ด้วยตัวคุณเอง:

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

หรือถ้าคุณชอบแบบฟอร์มสั้น ๆ เพิ่มเติม:

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

ตอนนี้สคริปต์ที่เหลือของคุณสามารถอ่านได้จาก stdin แน่นอนว่าคุณสามารถทำเช่นเดียวกันกับตัวเลือกการแยกวิเคราะห์ขั้นสูงมากกว่าการเข้ารหัสตำแหน่งของชื่อไฟล์อย่าง"$1"หนัก


execจะพยายามเรียกใช้อาร์กิวเมนต์เป็นคำสั่งซึ่งไม่ใช่สิ่งที่เราต้องการที่นี่
Suzana

@Suzana_K: ไม่เมื่อไม่มีข้อโต้แย้งเช่นที่นี่ ในกรณีนั้นมันจะแทนที่ file descriptors สำหรับเชลล์เองแทนที่จะเป็นกระบวนการลูก
..

ฉันคัดลอกif [ "$1" ] ; then exec < "$1" ; fiในสคริปต์ทดสอบและให้ข้อความแสดงข้อผิดพลาดเนื่องจากคำสั่งไม่รู้จัก เหมือนกันกับรูปแบบสั้น
Suzana

1
@Suzana_K: คุณใช้เปลือกอะไรอยู่? หากเป็นเช่นนั้นจริง ๆ แล้วมันไม่ใช่การนำไปปฏิบัติของ POSIX sh command / Bourne shell
R ..

GNU ทุบตี 4.3.11 บน Linux Mint Qiana
Suzana

3

ใช้ (หรือตัดออกจาก) อย่างอื่นที่มีพฤติกรรมนี้แล้วและใช้ "$@"

สมมติว่าฉันต้องการเขียนเครื่องมือที่จะแทนที่การรันของช่องว่างในข้อความด้วยแท็บ

trเป็นวิธีที่ชัดเจนที่สุดในการทำเช่นนี้ แต่ยอมรับเฉพาะ stdin เท่านั้นดังนั้นเราจึงต้องเชื่อมโยงจาก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'
$ 

สำหรับตัวอย่างที่เครื่องมือที่ใช้แล้วมีพฤติกรรมเช่นนี้เราสามารถนำสิ่งนี้ไปsedใช้แทน:

$ cat entab2.sh
#!/bin/sh

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

3

คุณยังสามารถทำสิ่งต่อไปนี้

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

สำหรับเทคนิคทดแทนพารามิเตอร์เรียบร้อยมากขึ้นในการทุบตีเห็นนี้


1
มันยอดเยี่ยมมาก! ฉันใช้uglifyjs < /dev/stdinงานได้ดีจริงๆ!
Fregante

0

คุณสามารถทำให้มันง่ายและใช้รหัสนี้


เมื่อคุณสร้างไฟล์สคริปต์pass_it_on.shด้วยรหัสนี้

#!/bin/bash

cat

คุณสามารถเรียกใช้

cat SOMEFILE.txt | ./pass_it_on.sh

และเนื้อหาทั้งหมดของ stdin จะถูกพ่นออกไปที่หน้าจอ


หรือใช้รหัสนี้เพื่อเก็บสำเนาของ stdin ไว้ในไฟล์แล้วพ่นออกไปที่หน้าจอ

#!/bin/bash

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

และนี่คืออีกตัวอย่างหนึ่งที่สามารถอ่านได้อธิบายเพิ่มเติมได้ที่นี่

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

#!/bin/bash

VALUE=$(cat)

echo "$VALUE"

มีความสุข.

RaamEE


0

วิธีที่ง่ายที่สุดและสอดคล้องกับ POSIX คือ:

file=${1--}

${1:--}ซึ่งเทียบเท่ากับ

จากนั้นอ่านไฟล์ตามปกติ:

while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.