ฉันมีคำสั่งที่ซับซ้อนที่ฉันต้องการสร้างเชลล์ / bash script ของ ฉันสามารถเขียน$1
ได้อย่างง่ายดาย:
foo $1 args -o $1.ext
ฉันต้องการส่งชื่ออินพุตหลาย ๆ ตัวให้สคริปต์ วิธีที่ถูกต้องในการทำคืออะไร?
และแน่นอนฉันต้องการจัดการชื่อไฟล์ด้วยช่องว่างในนั้น
ฉันมีคำสั่งที่ซับซ้อนที่ฉันต้องการสร้างเชลล์ / bash script ของ ฉันสามารถเขียน$1
ได้อย่างง่ายดาย:
foo $1 args -o $1.ext
ฉันต้องการส่งชื่ออินพุตหลาย ๆ ตัวให้สคริปต์ วิธีที่ถูกต้องในการทำคืออะไร?
และแน่นอนฉันต้องการจัดการชื่อไฟล์ด้วยช่องว่างในนั้น
คำตอบ:
ใช้"$@"
เพื่อเป็นตัวแทนของข้อโต้แย้งทั้งหมด:
for var in "$@"
do
echo "$var"
done
สิ่งนี้จะวนซ้ำแต่ละอาร์กิวเมนต์และพิมพ์ออกมาเป็นบรรทัดแยก $ @ ทำงานเหมือน $ * ยกเว้นว่าเมื่ออ้างถึงข้อโต้แย้งจะถูกทำลายอย่างถูกต้องหากมีช่องว่างในพวกเขา:
sh test.sh 1 2 '3 4'
1
2
3 4
"$@"
มันคือมันจะขยายตัวเพื่ออะไรเมื่อไม่มีพารามิเตอร์ตำแหน่งในขณะที่"$*"
ขยายไปยังสตริงที่ว่างเปล่า - และใช่มีความแตกต่างระหว่างไม่มีข้อโต้แย้งและอาร์กิวเมนต์ที่ว่างเปล่าอย่างใดอย่างหนึ่ง ดู/${1:+"$@"} in
bin / sh`
$var
ฉันสามารถใช้อาร์กิวเมนต์ได้
shift
โยนออกไป$1
แล้วเลื่อนองค์ประกอบที่ตามมาทั้งหมดลง
Rewrite ของตอนนี้ลบคำตอบโดยVonC
คำตอบสั้น ๆ ของRobert Gambleเกี่ยวข้องโดยตรงกับคำถาม อันนี้ขยายในบางประเด็นกับชื่อไฟล์ที่มีช่องว่าง
ดูเพิ่มเติม: $ {1: + "$ @"} ใน / bin / sh
วิทยานิพนธ์ขั้นพื้นฐาน: "$@"
ถูกต้องและ$*
(ไม่ได้อ้างอิง) เกือบจะผิดเสมอไป นี่เป็นเพราะใช้"$@"
งานได้ดีเมื่อข้อโต้แย้งมีช่องว่างและทำงานเหมือนกับ$*
เมื่อไม่มี ในบางสถานการณ์"$*"
ก็ใช้ได้เช่นกัน แต่"$@"
โดยปกติแล้ว (แต่ไม่เสมอไป) จะทำงานในที่เดียวกัน ไม่มีการอ้างอิง$@
และ$*
เทียบเท่า (และผิดเกือบทุกครั้ง)
ดังนั้นสิ่งที่เป็นความแตกต่างระหว่าง$*
, $@
, "$*"
และ"$@"
? พวกเขาทั้งหมดเกี่ยวข้องกับ 'อาร์กิวเมนต์ทั้งหมดของเชลล์' แต่พวกเขาทำสิ่งต่าง ๆ เมื่อไม่พูดถึง$*
และ$@
ทำในสิ่งเดียวกัน พวกเขาปฏิบัติต่อแต่ละคำว่า '' (ลำดับของช่องว่างที่ไม่ใช่ช่องว่าง) เป็นอาร์กิวเมนต์แยกต่างหาก รูปแบบที่ยกมามีความแตกต่างกันมาก แต่: "$*"
ถือว่ารายการอาร์กิวเมนต์เป็นสตริงที่คั่นด้วยช่องว่างเดียวในขณะที่"$@"
ถือว่าข้อโต้แย้งเกือบตรงตามที่พวกเขาเมื่อระบุไว้ในบรรทัดคำสั่ง
"$@"
ขยายไปสู่อะไรเลยเมื่อไม่มีข้อโต้แย้งตำแหน่ง; "$*"
ขยายเป็นสตริงว่าง - และใช่มีความแตกต่างแม้ว่ามันจะยากที่จะรับรู้ al
ดูข้อมูลเพิ่มเติมที่ด้านล่างหลังจากการแนะนำของคำสั่ง (ที่ไม่ได้มาตรฐาน)
วิทยานิพนธ์รอง:หากคุณต้องการประมวลผลอาร์กิวเมนต์ด้วยช่องว่างแล้วส่งต่อไปยังคำสั่งอื่นดังนั้นบางครั้งคุณต้องใช้เครื่องมือที่ไม่ได้มาตรฐานเพื่อช่วยเหลือ (หรือคุณควรใช้อาร์เรย์อย่างระมัดระวัง: "${array[@]}"
ทำงานแบบอะนาล็อกกับ"$@"
)
ตัวอย่าง:
$ mkdir "my dir" anotherdir
$ ls
anotherdir my dir
$ cp /dev/null "my dir/my file"
$ cp /dev/null "anotherdir/myfile"
$ ls -Fltr
total 0
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir/
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir/
$ ls -Fltr *
my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ ls -Fltr "./my dir" "./anotherdir"
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ var='"./my dir" "./anotherdir"' && echo $var
"./my dir" "./anotherdir"
$ ls -Fltr $var
ls: "./anotherdir": No such file or directory
ls: "./my: No such file or directory
ls: dir": No such file or directory
$
ทำไมจึงไม่ทำงาน มันไม่ทำงานเพราะเชลล์ประมวลผลอัญประกาศก่อนที่จะขยายตัวแปร ดังนั้นเพื่อให้เชลล์สนใจกับเครื่องหมายคำพูดที่ฝังอยู่$var
คุณต้องใช้eval
:
$ eval ls -Fltr $var
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$
สิ่งนี้จะยุ่งยากมากเมื่อคุณมีชื่อไฟล์เช่น " He said,
"Don't do this!"
" (พร้อมเครื่องหมายคำพูดและเครื่องหมายคำพูดคู่และช่องว่าง)
$ cp /dev/null "He said, \"Don't do this!\""
$ ls
He said, "Don't do this!" anotherdir my dir
$ ls -l
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 15:54 He said, "Don't do this!"
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir
$
กระสุน (ทั้งหมด) ไม่ได้ทำให้ง่ายต่อการจัดการกับสิ่งเหล่านั้นดังนั้น (อย่างพอเพียง) โปรแกรม Unix จำนวนมากไม่สามารถจัดการกับมันได้ บน Unix ชื่อไฟล์ (ส่วนหนึ่ง) สามารถมีตัวอักษรใด ๆ ยกเว้นเฉือนและ '\0'
NUL อย่างไรก็ตามเชลล์ไม่สนับสนุนการเว้นวรรคหรือการขึ้นบรรทัดใหม่หรือแท็บใด ๆ ในชื่อพา ธ มันเป็นสาเหตุที่ชื่อไฟล์ Unix มาตรฐานไม่มีช่องว่าง ฯลฯ
เมื่อต้องจัดการกับชื่อไฟล์ที่อาจมีช่องว่างและตัวอักษรที่มีปัญหาอื่น ๆ คุณต้องระวังอย่างมากและฉันพบเมื่อไม่นานมานี้ว่าฉันต้องการโปรแกรมที่ไม่ได้มาตรฐานบน Unix ฉันเรียกมันว่าescape
(เวอร์ชั่น 1.1 ลงวันที่ 1989-08-23T16: 01: 45Z)
นี่คือตัวอย่างของการescape
ใช้งาน - กับระบบควบคุม SCCS เป็นสคริปต์หน้าปกที่ทำทั้งdelta
(คิดว่าเช็คอิน ) และ
get
(คิดว่าเช็คเอาท์ ) ข้อโต้แย้งต่าง ๆ โดยเฉพาะ-y
(เหตุผลที่คุณทำการเปลี่ยนแปลง) จะมีช่องว่างและบรรทัดใหม่ โปรดทราบว่าสคริปต์วันที่จากปี 1992 ดังนั้นจึงใช้ back-ticks แทน
$(cmd ...)
สัญกรณ์และไม่ได้ใช้#!/bin/sh
ในบรรทัดแรก
: "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
# Delta and get files
# Uses escape to allow for all weird combinations of quotes in arguments
case `basename $0 .sh` in
deledit) eflag="-e";;
esac
sflag="-s"
for arg in "$@"
do
case "$arg" in
-r*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
-e) gargs="$gargs `escape \"$arg\"`"
sflag=""
eflag=""
;;
-*) dargs="$dargs `escape \"$arg\"`"
;;
*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
esac
done
eval delta "$dargs" && eval get $eflag $sflag "$gargs"
(ฉันอาจจะไม่ใช้การหลบหลีกค่อนข้างมากในทุกวันนี้ - มันไม่จำเป็นกับการ-e
โต้แย้งเช่น - แต่โดยรวมแล้วนี่เป็นหนึ่งในสคริปต์ที่เรียบง่ายของฉันโดยใช้escape
)
escape
โปรแกรมเพียงแค่เอาท์พุทอาร์กิวเมนต์ค่อนข้างชอบecho
ไม่ แต่จะตรวจสอบว่าข้อโต้แย้งที่ได้รับความคุ้มครองสำหรับใช้กับ
eval
(หนึ่งระดับของeval
ฉันจะมีโปรแกรมที่ได้ดำเนินการระยะไกลเปลือกและที่จำเป็นในการหลบหนีการส่งออกของescape
)
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
ฉันมีโปรแกรมอื่นที่เรียกal
ว่าแสดงรายการอาร์กิวเมนต์หนึ่งรายการต่อบรรทัด (และมันเก่ากว่ามาก: เวอร์ชัน 1.1 ลงวันที่ 1987-01-27T14: 35: 49) มันมีประโยชน์มากที่สุดเมื่อทำการดีบักสคริปต์เนื่องจากสามารถเสียบเข้ากับบรรทัดคำสั่งเพื่อดูว่าอาร์กิวเมนต์ใดที่ส่งผ่านไปยังคำสั่งจริง
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[ เพิ่ม:
และตอนนี้เพื่อแสดงความแตกต่างระหว่าง"$@"
สัญลักษณ์ต่าง ๆนี่คืออีกตัวอย่าง:
$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh * */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$
ขอให้สังเกตว่าไม่มีอะไรรักษาช่องว่างดั้งเดิมระหว่าง*
และ*/*
บนบรรทัดคำสั่ง นอกจากนี้โปรดทราบว่าคุณสามารถเปลี่ยน 'อาร์กิวเมนต์บรรทัดคำสั่ง' ในเชลล์โดยใช้:
set -- -new -opt and "arg with space"
ชุดนี้มี 4 ตัวเลือก ' -new
', ' -opt
', ' and
' และ ' arg with space
'
]
อืมนั่นเป็นคำตอบที่ค่อนข้างยาว- บางทีการตีความอาจเป็นคำที่ดีกว่า ซอร์สโค้ดสำหรับescape
ตามคำขอ (อีเมลถึงชื่อนามสกุลที่ gmail dot com) ซอร์สโค้ดสำหรับal
นั้นง่ายมาก:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
นั่นคือทั้งหมดที่ มันเทียบเท่ากับtest.sh
สคริปต์ที่ Robert Gamble แสดงและสามารถเขียนเป็นฟังก์ชั่นเชลล์ได้ (แต่ฟังก์ชั่นเชลล์ไม่มีอยู่ในบอร์นเชลล์รุ่นท้องถิ่นเมื่อฉันเขียนครั้งแรกal
)
โปรดทราบว่าคุณสามารถเขียนal
เป็นสคริปต์เชลล์แบบง่าย ๆ ได้:
[ $# != 0 ] && printf "%s\n" "$@"
เงื่อนไขเป็นสิ่งจำเป็นเพื่อสร้างเอาต์พุตไม่เมื่อไม่มีการขัดแย้ง printf
คำสั่งจะผลิตบรรทัดว่างมีเพียงอาร์กิวเมนต์สตริงรูปแบบ แต่โปรแกรม C ผลิตอะไร
โปรดทราบว่าคำตอบของ Robert นั้นถูกต้องและสามารถใช้ได้sh
เช่นกัน คุณสามารถลดความซับซ้อนของมันลงไปอีก:
for i in "$@"
เทียบเท่ากับ:
for i
คือคุณไม่ต้องการอะไร!
การทดสอบ ( $
พร้อมรับคำสั่ง):
$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d
ฉันแรกอ่านเกี่ยวกับเรื่องนี้ในUnix Programming Environmentโดย Kernighan และ Pike
ในbash
, help for
เอกสารนี้:
for NAME [in WORDS ... ;] do COMMANDS; done
หาก
'in WORDS ...;'
ไม่มีอยู่ก็'in "$@"'
จะถือว่า
"$@"
วิธีการและเมื่อคุณรู้ว่าสิ่งที่หมายถึงมันเป็นไม่น้อยกว่าอ่านfor i
for i in "$@"
for i
ดีกว่าเพราะมันช่วยกดแป้นพิมพ์ ผมเทียบการอ่านของและfor i
for i in "$@"
shift
สำหรับกรณีที่เรียบง่ายคุณยังสามารถใช้ มันถือว่ารายการอาร์กิวเมนต์เช่นคิว แต่ละshift
โยนอาร์กิวเมนต์แรกออกและดัชนีของแต่ละอาร์กิวเมนต์ที่เหลือจะลดลง
#this prints all arguments
while test $# -gt 0
do
echo "$1"
shift
done
shift
แบบวนรอบองค์ประกอบนั้นยังคงเป็นส่วนหนึ่งของอาเรshift
ย์
echo "$1"
เพื่อรักษาระยะห่างในค่าของสตริงอาร์กิวเมนต์ นอกจากนี้ (เป็นปัญหาเล็กน้อย) สิ่งนี้เป็นอันตราย: คุณไม่สามารถนำข้อโต้แย้งกลับมาใช้ใหม่ได้หากคุณเลื่อนมันไปทั้งหมด บ่อยครั้งที่นั่นไม่ใช่ปัญหาคุณประมวลผลรายการอาร์กิวเมนต์เพียงครั้งเดียว
--file myfile.txt
$ 1 คือพารามิเตอร์ $ 2 คือค่าและคุณเรียก shift เป็นสองครั้งเมื่อคุณต้องการข้ามอาร์กิวเมนต์อื่น
นอกจากนี้คุณยังสามารถเข้าถึงพวกเขาเป็นองค์ประกอบอาร์เรย์ตัวอย่างเช่นถ้าคุณไม่ต้องการที่จะทำซ้ำพวกเขาทั้งหมด
argc=$#
argv=("$@")
for (( j=0; j<argc; j++ )); do
echo "${argv[j]}"
done
./my-script.sh param1 "param2 is a quoted param"
: - ใช้ argv = ($ @) คุณจะได้รับ [param1, param2] - ใช้ argv = ("$ @") คุณจะได้รับ [param1, param2 เป็นพารามิเตอร์ที่ยกมา]
aparse() {
while [[ $# > 0 ]] ; do
case "$1" in
--arg1)
varg1=${2}
shift
;;
--arg2)
varg2=true
;;
esac
shift
done
}
aparse "$@"
[ $# != 0 ]
จะดีกว่า ในข้างต้น#$
ควรจะ$#
เป็นในwhile [[ $# > 0 ]] ...
การขยายคำตอบของ baz หากคุณต้องการระบุรายการอาร์กิวเมนต์ด้วยดัชนี (เช่นเพื่อค้นหาคำที่เฉพาะเจาะจง) คุณสามารถทำได้โดยไม่ต้องคัดลอกรายการหรือทำให้กลายพันธุ์
สมมติว่าคุณต้องการแยกรายการอาร์กิวเมนต์ที่ double-dash ("-") และส่งอาร์กิวเมนต์ก่อนเครื่องหมายขีดคั่นหนึ่งคำสั่งและอาร์กิวเมนต์หลังจากเครื่องหมายขีดคั่นไปยังอีก:
toolwrapper() {
for i in $(seq 1 $#); do
[[ "${!i}" == "--" ]] && break
done || return $? # returns error status if we don't "break"
echo "dashes at $i"
echo "Before dashes: ${@:1:i-1}"
echo "After dashes: ${@:i+1:$#}"
}
ผลลัพธ์ควรมีลักษณะเช่นนี้:
$ toolwrapper args for first tool -- and these are for the second
dashes at 5
Before dashes: args for first tool
After dashes: and these are for the second
getoptใช้คำสั่งในสคริปต์ของคุณเพื่อจัดรูปแบบตัวเลือกบรรทัดคำสั่งหรือพารามิเตอร์ใด ๆ
#!/bin/bash
# Extract command line options & values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift ;;
-c) echo "Found the -c option" ;;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift