บอกว่าฉันมีไฟล์ที่foo.txt
ระบุN
ข้อโต้แย้ง
arg1
arg2
...
argN
ซึ่งฉันต้องผ่านไปยังคำสั่ง my_command
ฉันจะใช้บรรทัดของไฟล์เป็นอาร์กิวเมนต์ของคำสั่งได้อย่างไร
บอกว่าฉันมีไฟล์ที่foo.txt
ระบุN
ข้อโต้แย้ง
arg1
arg2
...
argN
ซึ่งฉันต้องผ่านไปยังคำสั่ง my_command
ฉันจะใช้บรรทัดของไฟล์เป็นอาร์กิวเมนต์ของคำสั่งได้อย่างไร
คำตอบ:
หากเชลล์ของคุณทุบตี (ท่ามกลางคนอื่น) ทางลัดสำหรับ$(cat afile)
คือ$(< afile)
ดังนั้นคุณจะเขียน:
mycommand "$(< file.txt)"
เอกสารในหน้า bash man ในส่วน 'การทดแทนคำสั่ง'
เปลี่ยนคำสั่งของคุณจาก stdin ดังนั้น: mycommand < file.txt
<
โอเปอเรเตอร์ หมายความว่าเชลล์เองจะทำการเปลี่ยนเส้นทางแทนที่จะดำเนินการcat
ไบนารีสำหรับคุณสมบัติการเปลี่ยนเส้นทาง
"$(…)"
เนื้อหาของfile.txt
จะถูกส่งผ่านไปยังอินพุตมาตรฐานของmycommand
ไม่ใช่อาร์กิวเมนต์ "$(…)"
วิธีการเรียกใช้คำสั่งและให้กลับออกเป็นสตริง ; ที่นี่“ คำสั่ง” เท่านั้นอ่านไฟล์ แต่มันอาจจะซับซ้อนมากขึ้น
ดังกล่าวแล้วคุณสามารถใช้ backticks $(cat filename)
หรือ
สิ่งที่ไม่ได้กล่าวถึงและฉันคิดว่าสิ่งสำคัญที่ควรทราบคือคุณต้องจำไว้ว่าเชลล์จะแยกเนื้อหาของไฟล์นั้นตามช่องว่างโดยให้คำว่า "คำ" แต่ละคำพบว่าคำสั่งของคุณเป็นอาร์กิวเมนต์ และในขณะที่คุณอาจจะสามารถใส่อาร์กิวเมนต์บรรทัดคำสั่งในเครื่องหมายคำพูดเพื่อให้สามารถมีช่องว่าง, ลำดับหนี, ฯลฯ การอ่านจากไฟล์จะไม่ทำสิ่งเดียวกัน ตัวอย่างเช่นหากไฟล์ของคุณมี:
a "b c" d
ข้อโต้แย้งที่คุณจะได้รับคือ:
a
"b
c"
d
หากคุณต้องการดึงแต่ละบรรทัดเป็นอาร์กิวเมนต์ให้ใช้ while / read / do construct:
while read i ; do command_name $i ; done < filename
read -r
ถ้าคุณไม่ต้องการขยายลำดับแบ็กสแลช - เอสเคปและ NUL เป็นตัวคั่นที่ปลอดภัยกว่าการใช้งานขึ้นบรรทัดใหม่โดยเฉพาะอย่างยิ่งถ้าอาร์กิวเมนต์ที่คุณส่งผ่านนั้นเป็นชื่อไฟล์ซึ่งอาจมีบรรทัดใหม่ นอกจากนี้โดยไม่ต้องล้าง IFS i
คุณจะได้รับชั้นนำและต่อท้ายช่องว่างเคลียร์โดยปริยายจาก
command `< file`
จะส่งเนื้อหาของไฟล์ไปยังคำสั่งบน stdin แต่จะตัดการขึ้นบรรทัดใหม่ซึ่งหมายความว่าคุณไม่สามารถทำซ้ำแต่ละบรรทัดได้ เพื่อที่คุณจะสามารถเขียนสคริปต์ด้วย 'for' loop:
for line in `cat input_file`; do some_command "$line"; done
หรือ (ตัวแปรหลายบรรทัด):
for line in `cat input_file`
do
some_command "$line"
done
หรือ (ตัวแปรหลายบรรทัดพร้อม$()
แทน``
):
for line in $(cat input_file)
do
some_command "$line"
done
< file
backticks หมายความว่ามันไม่ได้ทำการเปลี่ยนเส้นทางcommand
เลย
command `< file`
ไม่ไม่ทำงานใน bash หรือ POSIX sh; zsh เป็นเรื่องที่แตกต่างกัน
Hello World
หนึ่งบรรทัด คุณจะเห็นมันทำงานครั้งแรกsome_command Hello
แล้วsome_command World
, ไม่ได้ หรือsome_command "Hello World"
some_command Hello World
คุณทำเช่นนั้นโดยใช้ backticks:
echo World > file.txt
echo Hello `cat file.txt`
echo "Hello * Starry * World" > file.txt
ในขั้นตอนแรกคุณจะได้รับอาร์กิวเมนต์อย่างน้อยสี่แยกผ่านไปยังคำสั่งที่สอง - และมีแนวโน้ม เพิ่มเติมตามที่*
s จะขยายไปยังชื่อของไฟล์ที่มีอยู่ในไดเรกทอรีปัจจุบัน
fork()
ปิด subshell ด้วย FIFO ที่แนบมากับ stdout แล้วเรียกใช้/bin/cat
เป็นลูกของ subshell นั้นจากนั้นอ่านเอาต์พุตผ่าน FIFO; เปรียบเทียบกับ$(<file.txt)
ซึ่งอ่านเนื้อหาของไฟล์เป็นการทุบตีโดยตรงโดยไม่ต้องมี subshells หรือ FIFO ที่เกี่ยวข้อง
หากคุณต้องการทำสิ่งนี้ในวิธีที่มีประสิทธิภาพซึ่งใช้ได้กับทุกอาร์กิวเมนต์บรรทัดคำสั่งที่เป็นไปได้ (ค่าที่มีช่องว่างค่าที่มีการขึ้นบรรทัดใหม่ค่าที่มีอักขระเครื่องหมายคำพูดตัวอักษรค่าที่พิมพ์ไม่ได้ค่าที่มีตัวอักษรกลม ฯลฯ ) น่าสนใจมากขึ้น.
ในการเขียนไฟล์ให้กำหนดอาเรย์ของอาร์กิวเมนต์:
printf '%s\0' "${arguments[@]}" >file
... เปลี่ยน"argument one"
, "argument two"
ฯลฯ ตามความเหมาะสม
หากต้องการอ่านจากไฟล์นั้นและใช้เนื้อหา (ใน bash, ksh93 หรือเชลล์ล่าสุดที่มีอาร์เรย์):
declare -a args=()
while IFS='' read -r -d '' item; do
args+=( "$item" )
done <file
run_your_command "${args[@]}"
หากต้องการอ่านจากไฟล์นั้นและใช้เนื้อหาของมัน (ในเชลล์ที่ไม่มีอาร์เรย์โปรดทราบว่าสิ่งนี้จะเขียนทับรายการอาร์กิวเมนต์บรรทัดคำสั่งในเครื่องของคุณและทำได้ดีที่สุดในฟังก์ชั่นเช่นคุณเขียนทับอาร์กิวเมนต์ของฟังก์ชัน รายการทั่วโลก):
set --
while IFS='' read -r -d '' item; do
set -- "$@" "$item"
done <file
run_your_command "$@"
โปรดทราบว่า-d
(การอนุญาตให้ใช้ตัวคั่นจุดสิ้นสุดบรรทัดอื่น) เป็นส่วนขยายที่ไม่ใช่ POSIX และเชลล์ที่ไม่มีอาร์เรย์อาจไม่สนับสนุน ในกรณีนี้คุณอาจต้องใช้ภาษาที่ไม่ใช่เชลล์เพื่อแปลงเนื้อหาที่คั่นด้วย NUL ให้อยู่ในeval
รูปแบบที่ปลอดภัย:
quoted_list() {
## Works with either Python 2.x or 3.x
python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
'
}
eval "set -- $(quoted_list <file)"
run_your_command "$@"
นี่คือวิธีที่ฉันส่งเนื้อหาของไฟล์เป็นอาร์กิวเมนต์ไปยังคำสั่ง:
./foo --bar "$(cat ./bar.txt)"
$(<bar.txt)
(เมื่อใช้เชลล์เช่นทุบตีด้วยส่วนขยายที่เหมาะสม) $(<foo)
เป็นกรณีพิเศษ: ซึ่งแตกต่างจากการทดแทนคำสั่งปกติตามที่ใช้ใน$(cat ...)
มันไม่แยกออก subshell เพื่อทำงานในและจึงหลีกเลี่ยงการใช้ค่าใช้จ่ายมาก
หากสิ่งที่คุณต้องทำคือเปิดไฟล์arguments.txt
ด้วยเนื้อหา
arg1
arg2
argN
ลงไปmy_command arg1 arg2 argN
แล้วคุณสามารถใช้xargs
:
xargs -a arguments.txt my_command
คุณสามารถใส่อาร์กิวเมนต์เพิ่มเติมแบบคงที่ในการxargs
โทรเช่นxargs -a arguments.txt my_command staticArg
ที่จะโทรmy_command staticArg arg1 arg2 argN
ในเปลือกทุบตีของฉันต่อไปนี้ทำงานเหมือนมีเสน่ห์:
cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'
โดยที่ input_file คือ
arg1
arg2
arg3
ในฐานะที่เป็นที่เห็นได้ชัดนี้จะช่วยให้คุณสามารถรันคำสั่งหลายกับแต่ละบรรทัดจาก input_file, เคล็ดลับเล็ก ๆ น้อย ๆ ที่ดีผมได้เรียนรู้ที่นี่
$(somecommand)
คุณจะได้รับคำสั่งนั้นแทนที่จะถูกส่งผ่านเป็นข้อความ ในทำนองเดียวกัน>/etc/passwd
จะถูกประมวลผลเป็นการเปลี่ยนเส้นทางและเขียนทับ/etc/passwd
(หากทำงานด้วยสิทธิ์ที่เหมาะสม) และอื่น ๆ
xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _
และมีประสิทธิภาพมากกว่าเนื่องจากส่งผ่านอาร์กิวเมนต์จำนวนมากไปยังแต่ละเชลล์มากที่สุดเท่าที่จะทำได้แทนที่จะเริ่มต้นหนึ่งเชลล์ต่อบรรทัดในไฟล์อินพุตของคุณ
cat
หลังจากแก้ไขคำตอบของ @Wesley Riceสองครั้งฉันตัดสินใจว่าการเปลี่ยนแปลงของฉันใหญ่เกินไปที่จะเปลี่ยนคำตอบของเขาต่อไปแทนที่จะเขียนเอง ฉันเลยตัดสินใจเขียนเอง!
อ่านแต่ละบรรทัดของไฟล์ในและดำเนินการกับมันทีละบรรทัดเช่นนี้:
#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
echo "$line"
done < "$input"
นี้มาโดยตรงจากผู้เขียนวิเวก Gite ที่นี่: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ เขาได้รับเครดิต!
ไวยากรณ์: อ่านบรรทัดไฟล์ต่อบรรทัดบนเชลล์ Bash Unix & Linux:
1. ไวยากรณ์เป็นดังนี้สำหรับ bash, ksh, zsh และเชลล์อื่น ๆ ทั้งหมดเพื่ออ่านไฟล์บรรทัดต่อบรรทัด
2while read -r line; do COMMAND; done < input.file
3.-r
ตัวเลือกที่ส่งไปยังคำสั่งอ่าน ป้องกัน backslash escapes จากการตีความ
4. เพิ่มIFS=
ตัวเลือกก่อนที่จะอ่านคำสั่งเพื่อป้องกันช่องว่างนำหน้า / ต่อท้ายไม่ให้ถูกตัด -
5while IFS= read -r line; do COMMAND_on $line; done < input.file
และตอนนี้เพื่อตอบคำถามที่ปิดไปแล้วซึ่งฉันมี: เป็นไปได้หรือไม่ที่จะ "git เพิ่ม" รายชื่อไฟล์จากไฟล์? - นี่คือคำตอบของฉัน:
ทราบว่าFILES_STAGED
เป็นตัวแปรที่มีเส้นทางสัมบูรณ์ไปยังไฟล์ที่มีพวงของสายซึ่งแต่ละสายเป็นเส้นทางเทียบกับไฟล์ที่ผมอยากจะทำที่git add
บน ข้อมูลโค้ดนี้กำลังจะเป็นส่วนหนึ่งของไฟล์"eRCaGuy_dotfiles / helpful_scripts / sync_git_repo_to_build_machine.sh"ในโครงการนี้เพื่อเปิดใช้งานการซิงค์ไฟล์ที่ง่ายจากพีซีเครื่องหนึ่ง (เช่นคอมพิวเตอร์ที่ฉันใช้รหัส) เพิ่มเติมคอมพิวเตอร์ที่มีประสิทธิภาพในการสร้างผม): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles
while IFS= read -r line
do
echo " git add \"$line\""
git add "$line"
done < "$FILES_STAGED"
git add
กับมัน: เป็นไปได้หรือไม่ที่จะ“ git add`” รายการไฟล์จากไฟล์?