Viết lại câu trả lời đã bị xóa bởi VonC .
Câu trả lời ngắn gọn của Robert Gamble liên quan trực tiếp đến câu hỏi. Cái này khuếch đại một số vấn đề với tên tệp chứa khoảng trắng.
Xem thêm: $ {1: + "$ @"} trong / bin / sh
Luận án cơ bản: "$@"
là chính xác, và $*
(không trích dẫn) hầu như luôn luôn sai. Điều này là do "$@"
hoạt động tốt khi các đối số chứa khoảng trắng và hoạt động giống như $*
khi chúng không hoạt động. Trong một số trường hợp, "$*"
cũng ổn, nhưng "$@"
thường (nhưng không phải luôn luôn) hoạt động ở cùng một nơi. Không trích dẫn, $@
và $*
tương đương (và hầu như luôn luôn sai).
Vì vậy, sự khác biệt giữa là những gì $*
, $@
, "$*"
, và "$@"
? Tất cả chúng đều liên quan đến 'tất cả các đối số cho vỏ', nhưng chúng làm những việc khác nhau. Khi không được trích dẫn, $*
và $@
làm điều tương tự. Họ coi mỗi "từ" (chuỗi không phải khoảng trắng) là một đối số riêng biệt. Mặc dù vậy, các biểu mẫu được trích dẫn khá khác nhau: "$*"
coi danh sách đối số là một chuỗi được phân tách bằng dấu cách, trong khi "$@"
xử lý các đối số gần như chính xác khi chúng được chỉ định trên dòng lệnh.
"$@"
mở rộng thành không có gì cả khi không có đối số vị trí; "$*"
mở rộng thành một chuỗi trống - và vâng, có một sự khác biệt, mặc dù có thể khó nhận biết nó. Xem thêm thông tin bên dưới, sau khi giới thiệu lệnh (không chuẩn) al
.
Luận án phụ: nếu bạn cần xử lý các đối số với khoảng trắng và sau đó chuyển chúng sang các lệnh khác, thì đôi khi bạn cần các công cụ không chuẩn để hỗ trợ. (Hoặc bạn nên sử dụng mảng, cẩn thận: "${array[@]}"
hành xử tương tự "$@"
.)
Thí dụ:
$ 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
$
Tại sao nó không hoạt động? Nó không hoạt động vì shell xử lý dấu ngoặc kép trước khi nó mở rộng các biến. Vì vậy, để có được trình bao chú ý đến các trích dẫn được nhúng trong $var
, bạn phải sử dụng 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
$
Điều này thực sự khó khăn khi bạn có tên tệp như " He said,
"Don't do this!"
" (với dấu ngoặc kép và dấu ngoặc kép và dấu cách).
$ 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
$
Các shell (tất cả chúng) không làm cho nó dễ dàng xử lý những thứ như vậy, vì vậy (đủ vui) nhiều chương trình Unix không làm tốt việc xử lý chúng. Trên Unix, một tên tệp (thành phần đơn) có thể chứa bất kỳ ký tự nào ngoại trừ dấu gạch chéo và NUL '\0'
. Tuy nhiên, hệ vỏ khuyến khích mạnh mẽ không có khoảng trắng hoặc dòng mới hoặc tab ở bất kỳ đâu trong tên đường dẫn. Đó cũng là lý do tại sao tên tệp Unix tiêu chuẩn không chứa khoảng trắng, v.v.
Khi xử lý các tên tệp có thể chứa dấu cách và các ký tự rắc rối khác, bạn phải cực kỳ cẩn thận và từ lâu tôi đã thấy rằng tôi cần một chương trình không chuẩn trên Unix. Tôi gọi nó escape
(phiên bản 1.1 là ngày 1989-08-23T16: 01: 45Z).
Dưới đây là một ví dụ về escape
việc sử dụng - với hệ thống điều khiển SCCS. Nó là một kịch bản bao gồm cả delta
(nghĩ kiểm tra ) và
get
(nghĩ kiểm tra ). Nhiều đối số khác nhau, đặc biệt -y
(lý do tại sao bạn thực hiện thay đổi) sẽ chứa khoảng trống và dòng mới. Lưu ý rằng tập lệnh có từ năm 1992, vì vậy nó sử dụng dấu tick thay vì
$(cmd ...)
ký hiệu và không sử dụng #!/bin/sh
trên dòng đầu tiên.
: "@(#)$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"
(Tôi có thể sẽ không sử dụng lối thoát khá kỹ lưỡng trong những ngày này - chẳng hạn như không cần thiết với -e
đối số - nhưng về tổng thể, đây là một trong những tập lệnh đơn giản hơn của tôi sử dụng escape
.)
Các escape
chương trình chỉ đơn giản là kết quả đầu ra đối số của nó, chứ không phải như echo
hiện, nhưng nó đảm bảo rằng các đối số được bảo vệ để sử dụng với
eval
(một mức độ eval
, tôi không có một chương trình mà đã thực hiện remote shell, và điều đó cần thiết để thoát sản lượng escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Tôi có một chương trình khác được gọi là al
liệt kê các đối số của nó trên mỗi dòng (và nó thậm chí còn cổ hơn: phiên bản 1.1 ngày 1987-01-27T14: 35: 49). Nó hữu ích nhất khi gỡ lỗi các tập lệnh, vì nó có thể được cắm vào một dòng lệnh để xem những đối số nào thực sự được truyền cho lệnh.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[ Đã thêm:
Và bây giờ để hiển thị sự khác biệt giữa các "$@"
ký hiệu khác nhau , đây là một ví dụ nữa:
$ 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
$
Lưu ý rằng không có gì bảo tồn khoảng trống ban đầu giữa *
và */*
trên dòng lệnh. Ngoài ra, lưu ý rằng bạn có thể thay đổi 'đối số dòng lệnh' trong trình bao bằng cách sử dụng:
set -- -new -opt and "arg with space"
Điều này đặt 4 tùy chọn, ' -new
', ' -opt
', ' and
' và ' arg with space
'.
]
Hmm, đó là một câu trả lời khá dài - có lẽ exegesis là thuật ngữ tốt hơn. Mã nguồn escape
có sẵn theo yêu cầu (gửi email đến tên đầu tiên dấu chấm cuối cùng tại gmail dot com). Mã nguồn al
rất đơn giản:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Đó là tất cả. Nó tương đương với test.sh
kịch bản mà Robert Gamble đã trình bày và có thể được viết dưới dạng hàm shell (nhưng các hàm shell không tồn tại trong phiên bản shell của Bourne khi tôi viết lần đầu tiên al
).
Cũng lưu ý rằng bạn có thể viết al
dưới dạng một tập lệnh shell đơn giản:
[ $# != 0 ] && printf "%s\n" "$@"
Điều kiện là cần thiết để nó không tạo ra đầu ra khi thông qua không có đối số. Các printf
lệnh sẽ sản xuất một dòng trống chỉ với những lập luận chuỗi định dạng, nhưng chương trình C sản xuất gì cả.