สคริปต์ Perl ต่อไปนี้ ( my.pl
) สามารถอ่านได้จากไฟล์ในบรรทัดคำสั่ง args หรือจาก STDIN:
while (<>) {
print($_);
}
perl my.pl
จะอ่านจาก STDIN ในขณะที่จะอ่านจากperl my.pl a.txt
a.txt
มันสะดวกมาก
สงสัยว่ามี Bash เทียบเท่าใน?
สคริปต์ Perl ต่อไปนี้ ( my.pl
) สามารถอ่านได้จากไฟล์ในบรรทัดคำสั่ง args หรือจาก STDIN:
while (<>) {
print($_);
}
perl my.pl
จะอ่านจาก STDIN ในขณะที่จะอ่านจากperl my.pl a.txt
a.txt
มันสะดวกมาก
สงสัยว่ามี Bash เทียบเท่าใน?
คำตอบ:
โซลูชันต่อไปนี้อ่านจากไฟล์หากสคริปต์ถูกเรียกด้วยชื่อไฟล์เป็นพารามิเตอร์แรก$1
มิฉะนั้นจากอินพุตมาตรฐาน
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
การทดแทน${1:-...}
ใช้$1
ถ้ากำหนดไว้มิฉะนั้นจะใช้ชื่อไฟล์ของอินพุตมาตรฐานของกระบวนการของตัวเอง
/proc/$$/fd/0
และ/dev/stdin
? ฉันสังเกตว่าหลังดูจะธรรมดากว่าและดูตรงไปกว่า
-r
ของคุณread
เพื่อที่จะไม่กิน\
ตัวอักษรโดยไม่ตั้งใจ; ใช้while IFS= read -r line
เพื่อรักษาพื้นที่ว่างชั้นนำและต่อท้าย
/bin/sh
- คุณใช้เปลือกนอกbash
หรือsh
ไม่
บางทีทางออกที่ง่ายที่สุดคือการเปลี่ยนเส้นทาง stdin ด้วยการรวมตัวดำเนินการเปลี่ยนเส้นทาง:
#!/bin/bash
less <&0
Stdin คือ file descriptor zero ด้านบนจะส่งอินพุตที่ส่งไปยังสคริปต์ทุบตีของคุณลงใน stdin ที่น้อยลง
<&0
ในสถานการณ์นี้ - ตัวอย่างของคุณจะทำงานแบบเดียวกันกับที่มีหรือไม่มี - ดูเหมือนว่าเครื่องมือที่คุณเรียกใช้จากภายในสคริปต์ทุบตีโดยค่าเริ่มต้นจะดู stdin เช่นเดียวกับสคริปต์ (เว้นแต่สคริปต์จะใช้ก่อน)
นี่คือวิธีที่ง่ายที่สุด:
#!/bin/sh
cat -
การใช้งาน:
$ echo test | sh my_script.sh
test
ในการกำหนดstdinให้กับตัวแปรคุณอาจใช้: STDIN=$(cat -)
หรือเพียงแค่STDIN=$(cat)
ไม่จำเป็นต้องใช้โอเปอเรเตอร์ (ตามความคิดเห็น @ mklement0 )
ในการแยกแต่ละบรรทัดจากอินพุตมาตรฐานลองสคริปต์ต่อไปนี้:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
หากต้องการอ่านจากไฟล์หรือstdin (หากไม่มีอาร์กิวเมนต์) คุณสามารถขยายเป็น:
#!/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")
หมายเหตุ:
-
read -r
- อย่าใช้อักขระแบ็กสแลชในลักษณะพิเศษใด ๆ พิจารณาแบ็กสแลชแต่ละรายการเพื่อเป็นส่วนหนึ่งของบรรทัดอินพุต- โดยไม่ต้องตั้งค่า
IFS
เริ่มต้นลำดับของSpaceและTabที่จุดเริ่มต้นและจุดสิ้นสุดของบรรทัดจะถูกละเว้น (ตัด)- ใช้
printf
แทนecho
เพื่อหลีกเลี่ยงการพิมพ์บรรทัดว่างเมื่อสายประกอบด้วยเดียว-e
, หรือ-n
-E
อย่างไรก็ตามมีวิธีแก้ปัญหาโดยการใช้env POSIXLY_CORRECT=1 echo "$line"
ซึ่งเรียกใช้ GNU ภายนอกของคุณecho
ซึ่งรองรับ ดู: ฉันจะสะท้อน "-e" ได้อย่างไร?
โปรดดู: วิธีการอ่าน stdin เมื่อไม่มีการส่งผ่านอาร์กิวเมนต์? ที่ stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
FILE=${1:--}
(Quibble: ดีกว่าเพื่อหลีกเลี่ยงตัวแปรเชลล์ตัวพิมพ์ใหญ่ทั้งหมดเพื่อหลีกเลี่ยงการชนชื่อกับตัวแปรสภาพแวดล้อม )
${1:--}
เป็นไปตาม POSIX ดังนั้นจึงควรทำงานกับเชลล์ที่มีลักษณะคล้าย POSIX ทั้งหมด สิ่งที่ใช้ไม่ได้ในเชลล์เหล่านี้คือการทดแทนโปรเซส ( <(...)
); มันจะทำงานในทุบตี, ksh, zsh แต่ไม่ได้อยู่ในเส้นประตัวอย่างเช่น นอกจากนี้ควรเพิ่มคำสั่ง-r
ของคุณread
เพื่อที่จะไม่กิน\
ตัวอักษรโดยไม่ตั้งใจ เตรียมที่IFS=
จะรักษาช่องว่างชั้นนำและต่อท้าย
echo
ถ้าบรรทัดประกอบด้วย-e
, -n
หรือ-E
มันจะไม่ปรากฏ เพื่อแก้ไขปัญหานี้คุณต้องใช้:printf
printf '%s\n' "$line"
ฉันไม่ได้รวมไว้ในการแก้ไขก่อนหน้า ... บ่อยเกินไปการแก้ไขของฉันจะย้อนกลับเมื่อฉันแก้ไขข้อผิดพลาด:(
นี้
--
ไม่มีประโยชน์อะไรถ้าอาร์กิวเมนต์แรกคือ'%s\n'
IFS=
กับread
และแทนprintf
. echo
:)
ฉันคิดว่านี่เป็นวิธีที่ตรงไปตรงมา:
$ 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
อ่านจาก stdin โดยค่าเริ่มต้นจึงมีความจำเป็นต้อง< /dev/stdin
สำหรับ
การecho
แก้ปัญหาจะเพิ่มบรรทัดใหม่เมื่อใดก็ตามที่IFS
แบ่งกระแสอินพุต คำตอบของ @fgmสามารถแก้ไขได้เล็กน้อย:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
พฤติกรรมในขณะที่read
ไม่อาจแยกออกเป็นราชสกุลหลายคนโดยตัวอักษร มีอยู่ใน$IFS
มันจะส่งกลับโทเค็นเดียวถ้าคุณระบุชื่อตัวแปรเดียวเท่านั้น (แต่จดจ้องและนำหน้าและช่องว่างตามค่าเริ่มต้น)
read
และ$IFS
- echo
ตัวเองเพิ่มบรรทัดใหม่โดยไม่มีการ-n
ตั้งค่าสถานะ "ยูทิลิตี echo เขียนตัวถูกดำเนินการที่ระบุใด ๆ คั่นด้วยอักขระว่างเปล่า (` ') เดี่ยวและตามด้วยอักขระขึ้นบรรทัดใหม่ (`\ n') ไปยังเอาต์พุตมาตรฐาน"
\n
เพิ่มโดยecho
: Perl's $_
รวมถึงบรรทัดที่ลงท้ายด้วย\n
บรรทัดที่อ่านในขณะที่ทุบตีread
ไม่ได้ (อย่างไรก็ตามเนื่องจาก @gniourf_gniourf ชี้ให้เห็นที่อื่น ๆ วิธีการที่แข็งแกร่งกว่าคือการใช้printf '%s\n'
แทนecho
)
Perl ห่วงในคำถามอ่านจากอาร์กิวเมนต์ชื่อไฟล์ทั้งหมดบนบรรทัดคำสั่งหรือจากอินพุตมาตรฐานหากไม่มีการระบุไฟล์ คำตอบที่ฉันเห็นทั้งหมดดูเหมือนว่าจะประมวลผลไฟล์เดียวหรืออินพุตมาตรฐานหากไม่มีการระบุไฟล์
แม้ว่ามักจะเยาะเย้ยอย่างถูกต้องเป็นUUOC (การใช้งานที่ไร้ประโยชน์cat
) มีบางครั้งที่cat
เป็นเครื่องมือที่ดีที่สุดสำหรับงานและมันก็พิสูจน์ได้ว่านี่เป็นหนึ่งในพวกเขา:
cat "$@" |
while read -r line
do
echo "$line"
done
ข้อเสียเพียงอย่างเดียวคือสร้างไปป์ไลน์ที่ทำงานใน sub-shell ดังนั้นสิ่งต่าง ๆ เช่นการกำหนดตัวแปรในwhile
ลูปจะไม่สามารถเข้าถึงได้นอกไพพ์ไลน์ bash
ทางรอบที่เป็นกระบวนการชดเชย :
while read -r line
do
echo "$line"
done < <(cat "$@")
สิ่งนี้จะทำให้การwhile
วนซ้ำทำงานในเชลล์หลักดังนั้นตัวแปรที่ตั้งในลูปจะสามารถเข้าถึงได้นอกลูป
>>EOF\n$(cat "$@")\nEOF
บรรทัด) ในที่สุดการเล่นลิ้น: while IFS= read -r line
เป็นการประมาณที่ดีขึ้นของสิ่งที่while (<>)
เกิดขึ้นใน Perl (เก็บรักษาช่องว่างชั้นนำและต่อท้าย - แม้ว่า Perl ยังรักษาเส้นทาง\n
)
พฤติกรรมของ Perl ด้วยรหัสที่ให้ใน OP ไม่สามารถมีอาร์กิวเมนต์ได้หลายข้อและถ้าอาร์กิวเมนต์เป็นยัติภังค์เดียว-
ก็เข้าใจได้ว่าเป็น stdin $ARGV
นอกจากนี้ก็มักจะเป็นไปได้ที่จะมีชื่อไฟล์ที่มี ไม่มีคำตอบใด ๆ ที่เลียนแบบพฤติกรรมของ Perl ในแง่เหล่านี้ นี่คือความเป็นไปได้ของ Bash ที่บริสุทธิ์ เคล็ดลับคือการใช้exec
อย่างเหมาะสม
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
$1
ในชื่อไฟล์ที่มีอยู่ของ
หากไม่มีข้อโต้แย้งใด ๆ เราจะตั้งค่า-
เป็นพารามิเตอร์ตำแหน่งแรกโดยไม่ตั้งใจ จากนั้นเราวนลูปกับพารามิเตอร์ ถ้าพารามิเตอร์ไม่ได้เป็นเราเปลี่ยนเส้นทางเข้ามาตรฐานจากชื่อไฟล์ด้วย-
exec
หากการเปลี่ยนเส้นทางนี้สำเร็จเราจะวนwhile
ซ้ำ ฉันใช้มาตรฐานตัวแปรและในกรณีนี้คุณไม่จำเป็นต้องตั้งค่าREPLY
IFS
หากคุณต้องการชื่ออื่นคุณต้องรีเซ็ตIFS
เช่นนั้น (เว้นแต่คุณไม่ต้องการและรู้ว่าคุณกำลังทำอะไร):
while IFS= read -r line; do
printf '%s\n' "$line"
done
แม่นยำยิ่งขึ้น ...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
และ-r
ไปยังread
มั่นใจว่าคำสั่งที่แต่ละบรรทัดจะอ่านไม่แปร (รวมชั้นนำและต่อท้ายช่องว่าง)
โปรดลองรหัสต่อไปนี้:
while IFS= read -r line; do
echo "$line"
done < file
read
โดยไม่ต้องIFS=
และ-r
และคนยากจน$line
ไม่ทราบราคาที่มีสุขภาพดี
read -r
สัญกรณ์ IMO, POSIX ผิดพลาด; ตัวเลือกควรเปิดใช้งานความหมายพิเศษสำหรับเครื่องหมายแบ็กสแลชต่อท้ายไม่ใช่ปิดใช้งาน - เพื่อให้สคริปต์ที่มีอยู่ (จากก่อน POSIX มีอยู่) จะไม่แตกเนื่องจาก-r
ถูกละเว้น อย่างไรก็ตามฉันสังเกตว่ามันเป็นส่วนหนึ่งของ IEEE 1003.2 1992 ซึ่งเป็นรุ่นแรกสุดของ POSIX เชลล์และมาตรฐานยูทิลิตี้ ฉันไม่เคยเจอปัญหาเพราะรหัสของฉันไม่ได้ใช้-r
; ฉันจะต้องโชคดี ไม่สนใจฉันในเรื่องนี้
-r
ควรจะเป็นมาตรฐาน ฉันยอมรับว่าไม่น่าเป็นไปได้ในกรณีที่ไม่ได้ใช้งานจะทำให้เกิดปัญหา แม้ว่ารหัสที่ใช้งานไม่ได้จะเป็นรหัสที่ใช้งานไม่ได้ การแก้ไขของฉันถูกเรียกครั้งแรกโดย$line
ตัวแปรที่ไม่ดีซึ่งไม่ได้รับเครื่องหมายคำพูด ฉันแก้ไขในread
ขณะที่ฉันอยู่ที่มัน ฉันไม่ได้แก้ไขecho
เพราะเป็นประเภทการแก้ไขที่ย้อนกลับได้ :(
.
รหัส${1:-/dev/stdin}
จะเข้าใจอาร์กิวเมนต์แรกดังนั้นเรื่องนี้
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
ฉันไม่พบคำตอบใด ๆ ที่ยอมรับได้ โดยเฉพาะคำตอบที่ตอบรับจะจัดการกับพารามิเตอร์บรรทัดคำสั่งแรกเท่านั้นและไม่สนใจส่วนที่เหลือ โปรแกรม Perl ที่พยายามเลียนแบบจัดการพารามิเตอร์บรรทัดคำสั่งทั้งหมด ดังนั้นคำตอบที่ยอมรับไม่ได้ตอบคำถาม คำตอบอื่น ๆ ใช้ส่วนขยาย bash เพิ่มคำสั่ง 'cat' ที่ไม่จำเป็นทำงานเฉพาะกับกรณีอย่างง่ายของการสะท้อนอินพุตไปยังเอาต์พุตหรือเพียงซับซ้อนโดยไม่จำเป็น
อย่างไรก็ตามฉันต้องให้เครดิตพวกเขาเพราะพวกเขาให้ความคิดกับฉัน นี่คือคำตอบที่สมบูรณ์:
#!/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
ฉันรวมคำตอบข้างต้นทั้งหมดและสร้างฟังก์ชันเชลล์ที่เหมาะกับความต้องการของฉัน นี่คือจากเทอร์มินัล cygwin ของเครื่อง Windows10 2 เครื่องของฉันซึ่งฉันมีโฟลเดอร์แชร์อยู่ระหว่างเครื่อง ฉันต้องสามารถจัดการกับสิ่งต่อไปนี้:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
ในกรณีที่ระบุชื่อไฟล์ที่เฉพาะเจาะจงฉันต้องใช้ชื่อไฟล์เดียวกันระหว่างการคัดลอก ในกรณีที่สตรีมข้อมูลอินพุตได้รับการส่งผ่านแล้วฉันต้องสร้างชื่อไฟล์ชั่วคราวโดยมีชั่วโมงนาทีและวินาที โฟลเดอร์หลักที่แชร์มีโฟลเดอร์ย่อยของวันในสัปดาห์ นี่คือเพื่อวัตถุประสงค์ขององค์กร
ดูเถิดสคริปต์ที่ดีที่สุดสำหรับความต้องการของฉัน:
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
}
หากมีวิธีใดที่คุณสามารถเห็นการเพิ่มประสิทธิภาพนี้ฉันอยากจะรู้
ผลงานต่อไปนี้มีมาตรฐานsh
(ทดสอบกับdash
เดเบียน) และอ่านได้ง่าย แต่นั่นเป็นเรื่องของรสนิยม:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
รายละเอียด: หากพารามิเตอร์แรกไม่ว่างเปล่าcat
ไฟล์นั้นจะเป็นcat
อินพุตมาตรฐานอื่น จากนั้นการส่งออกของทั้งงบการประมวลผลโดยif
commands_and_transformations
cat "${1:--}" | any_command
. การอ่านตัวแปร shell และการสะท้อนอาจใช้กับไฟล์ขนาดเล็กได้
[ -n "$1" ]
[ "$1" ]
อันนี้ใช้งานง่ายบนเครื่อง:
$ echo '1\n2\n3\n' | while read -r; do echo $REPLY; done
1
2
3
เกี่ยวกับ
for line in `cat`; do
something($line);
done
cat
จะถูกวางลงในบรรทัดคำสั่ง บรรทัดคำสั่งมีขนาดสูงสุด นอกจากนี้จะไม่อ่านทีละบรรทัด แต่คำต่อคำ