Переписати з тепер видалений відповідь по VonC .
Лаконічна відповідь Роберта Гембла безпосередньо стосується питання. Цей текст посилюється в деяких питаннях з назви файлів, що містять пробіли.
Дивіться також: $ {1: + "$ @"} в / бін / ш
Основна теза: "$@"
правильно, і $*
(без цитування) майже завжди неправильно. Це тому, що "$@"
працює добре, коли аргументи містять пробіли, і працює так само, як $*
коли їх немає. За певних обставин, "$*"
це також нормально, але "$@"
зазвичай (але не завжди) працює там же. Не цитуються $@
і $*
є рівнозначними (і майже завжди неправильними).
Отже, в чому різниця між $*
, $@
, "$*"
і "$@"
? Всі вони пов'язані з "усіма аргументами оболонки", але вони роблять різні речі. Коли некотируваних, $*
і $@
робити те ж саме. Вони розглядають кожне 'слово' (послідовність непробілу) як окремий аргумент. Проте, цитовані форми є зовсім різними: "$*"
трактує список аргументів як єдиний рядок, розділений пробілом, тоді "$@"
як аргументи трактує майже так, як вони були, коли вказано в командному рядку.
"$@"
розширюється взагалі до нічого, коли немає позиційних аргументів; "$*"
розширюється до порожнього рядка - і так, є різниця, хоча це може бути важко сприйняти. Детальну інформацію див. Нижче, після введення команди (нестандартної) 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 ім'я файлу (однокомпонентний) може містити будь-які символи, крім косої риски та NUL '\0'
. Однак оболонки наполегливо заохочують ні пробіли, ні рядки, ні вкладки ніде в назвах шляху. Ось чому, звичайні імена файлів Unix не містять пробілів тощо.
У роботі з іменами файлів, які можуть містити пробіли та інших проблемних символів, ви повинні бути дуже обережними, і я давно виявив, що мені потрібна програма, яка не є стандартною для Unix. Я називаю це escape
(версія 1.1 була датована 1989-08-23T16: 01: 45Z).
Ось приклад escape
використання - із системою управління SCCS. Це сценарій обкладинки, який виконує як delta
( зарахування ), так і
get
(обмірковане виїзд ). Різні аргументи, особливо -y
(причина, чому ви внесли зміни), міститимуть пробіли та нові рядки. Зауважте, що сценарій датується 1992 роком, тому він використовує зворотні $(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
сценарію, який показав Роберт Гембл, і його можна було записати як функцію оболонки (але функції оболонки не існували в локальній версії оболонки Борна, коли я вперше писав al
).
Також зауважте, що ви можете писати al
як простий скрипт оболонки:
[ $# != 0 ] && printf "%s\n" "$@"
Умовна необхідна, щоб вона не давала результатів, коли не було передано жодних аргументів. printf
Команда буде виробляти порожній рядок з тільки рядка формату аргументу, але програма C нічого не виробляє.