Tulis ulang jawaban yang sekarang dihapus oleh VonC .
Jawaban singkat Robert Gamble berkaitan langsung dengan pertanyaan itu. Yang ini menguatkan pada beberapa masalah dengan nama file yang mengandung spasi.
Lihat juga: $ {1: + "$ @"} di / bin / sh
Tesis dasar: "$@"
benar, dan $*
(tidak dikutip) hampir selalu salah. Ini karena "$@"
berfungsi dengan baik ketika argumen berisi spasi, dan berfungsi sama seperti $*
ketika mereka tidak. Dalam beberapa keadaan, "$*"
juga OK, tetapi "$@"
biasanya (tetapi tidak selalu) bekerja di tempat yang sama. Tidak dikutip, $@
dan $*
setara (dan hampir selalu salah).
Jadi, apa perbedaan antara $*
, $@
, "$*"
, dan "$@"
? Mereka semua terkait dengan 'semua argumen ke shell', tetapi mereka melakukan hal-hal yang berbeda. Ketika dikutip, $*
dan $@
lakukan hal yang sama. Mereka memperlakukan setiap 'kata' (urutan non-spasi putih) sebagai argumen terpisah. Bentuk-bentuk yang dikutip sangat berbeda, meskipun: "$*"
memperlakukan daftar argumen sebagai string yang dipisahkan oleh spasi, sedangkan "$@"
memperlakukan argumen hampir persis seperti ketika ditentukan pada baris perintah.
"$@"
tidak berkembang sama sekali ketika tidak ada argumen posisi; "$*"
meluas ke string kosong - dan ya, ada perbedaan, meskipun sulit untuk melihatnya. Lihat informasi lebih lanjut di bawah ini, setelah pengenalan perintah (non-standar) al
.
Tesis sekunder: jika Anda perlu memproses argumen dengan spasi dan kemudian meneruskannya ke perintah lain, maka Anda terkadang memerlukan alat yang tidak standar untuk membantu. (Atau Anda harus menggunakan array, dengan hati-hati: "${array[@]}"
berperilaku analog dengan "$@"
.)
Contoh:
$ 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
$
Mengapa itu tidak berhasil? Itu tidak berfungsi karena shell memproses tanda kutip sebelum memperluas variabel. Jadi, untuk mendapatkan shell agar memperhatikan kutipan yang tertanam di dalamnya $var
, Anda harus menggunakan 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
$
Ini menjadi sangat sulit ketika Anda memiliki nama file seperti " He said,
"Don't do this!"
" (dengan tanda kutip dan tanda kutip ganda dan spasi).
$ 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
$
Kerang (semuanya) tidak membuatnya sangat mudah untuk menangani hal-hal seperti itu, jadi (cukup lucu) banyak program Unix tidak melakukan pekerjaan yang baik untuk menanganinya. Pada Unix, nama file (komponen tunggal) dapat berisi karakter apa pun kecuali slash dan NUL '\0'
. Namun, shell sangat tidak mendorong spasi atau baris baru atau tab di mana pun dalam nama jalur. Itu juga mengapa nama file Unix standar tidak mengandung spasi, dll.
Ketika berhadapan dengan nama file yang mungkin mengandung spasi dan karakter merepotkan lainnya, Anda harus sangat berhati-hati, dan saya telah lama mengetahui bahwa saya memerlukan program yang tidak standar pada Unix. Saya menyebutnya escape
(versi 1.1 bertanggal 1989-08-23T16: 01: 45Z).
Berikut adalah contoh escape
penggunaan - dengan sistem kontrol SCCS. Ini adalah skrip sampul yang melakukan a delta
(think check-in ) dan a
get
(think check-out ). Berbagai argumen, terutama -y
(alasan mengapa Anda melakukan perubahan) akan berisi baris kosong dan baru. Perhatikan bahwa skrip berasal dari tahun 1992, sehingga menggunakan back-ticks sebagai ganti
$(cmd ...)
notasi dan tidak digunakan #!/bin/sh
pada baris pertama.
: "@(#)$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"
(Saya mungkin tidak akan menggunakan escape dengan sangat teliti akhir-akhir ini - itu tidak diperlukan dengan -e
argumen, misalnya - tetapi secara keseluruhan, ini adalah salah satu skrip saya yang lebih sederhana menggunakan escape
.)
The escape
Program hanya output argumen, agak seperti echo
tidak, tetapi memastikan bahwa argumen dilindungi untuk digunakan dengan
eval
(satu tingkat dari eval
; Saya punya sebuah program yang melakukan terpencil eksekusi shell, dan yang diperlukan untuk melarikan diri output escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Saya memiliki program lain yang disebut al
yang daftar argumennya satu per baris (dan itu bahkan lebih kuno: versi 1.1 tanggal 1987-01-27T14: 35: 49). Ini paling berguna ketika men-debug skrip, karena dapat dicolokkan ke baris perintah untuk melihat argumen apa yang sebenarnya diteruskan ke perintah.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[ Ditambahkan:
Dan sekarang untuk menunjukkan perbedaan antara berbagai "$@"
notasi, berikut adalah satu contoh lagi:
$ 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
$
Perhatikan bahwa tidak ada yang mempertahankan kosong asli antara *
dan */*
pada baris perintah. Juga, perhatikan bahwa Anda dapat mengubah 'argumen baris perintah' di shell dengan menggunakan:
set -- -new -opt and "arg with space"
Ini menetapkan 4 opsi, ' -new
', ' -opt
', ' and
', dan ' arg with space
'.
]
Hmm, itu jawaban yang cukup panjang - mungkin penafsiran adalah istilah yang lebih baik. Kode sumber escape
tersedia berdasarkan permintaan (email ke firstname dot lastname di gmail dot com). Kode sumber al
sangat sederhana:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Itu saja. Ini setara dengan test.sh
skrip yang diperlihatkan oleh Robert Gamble, dan dapat ditulis sebagai fungsi shell (tetapi fungsi shell tidak ada di versi lokal Bourne shell ketika saya pertama kali menulis al
).
Perhatikan juga bahwa Anda dapat menulis al
sebagai skrip shell sederhana:
[ $# != 0 ] && printf "%s\n" "$@"
Persyaratan diperlukan sehingga tidak menghasilkan output ketika melewati tidak ada argumen. The printf
perintah akan menghasilkan baris kosong dengan hanya argumen format string, tetapi program C menghasilkan apa-apa.