สคริปต์ 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 | txtx < file.cpptx 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อินพุตมาตรฐานอื่น จากนั้นการส่งออกของทั้งงบการประมวลผลโดยifcommands_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จะถูกวางลงในบรรทัดคำสั่ง บรรทัดคำสั่งมีขนาดสูงสุด นอกจากนี้จะไม่อ่านทีละบรรทัด แต่คำต่อคำ