Câu trả lời:
$ chạm ./-c $ 'a \ n12 \ tb' foo $ du -hs * 0 a 12 b 0 foo Tổng cộng 0
Như bạn có thể thấy, -c
tệp được lấy làm tùy chọn du
và không được báo cáo (và bạn thấy total
dòng này vì du -c
). Ngoài ra, tệp được gọi a\n12\tb
là làm cho chúng ta nghĩ rằng có các tệp được gọi a
và b
.
$ du -hs -- *
0 a
12 b
0 -c
0 foo
Cái đó tốt hơn. Ít nhất thời gian -c
này không được coi là một lựa chọn.
$ du -hs ./*
0 ./a
12 b
0 ./-c
0 ./foo
Điều đó thậm chí còn tốt hơn. Các ./
ngăn chặn tiền tố -c
khỏi mắc bẫy như một tùy chọn và sự vắng mặt của ./
trước b
trong đầu ra cho thấy rằng không có b
tập tin trong đó, nhưng có một tập tin với một ký tự xuống dòng (nhưng xem bên dưới 1 cho digressions thêm vào đó).
Cách tốt nhất là sử dụng ./
tiền tố khi có thể và nếu không và đối với dữ liệu tùy ý, bạn nên luôn luôn sử dụng:
cmd -- "$var"
hoặc là:
cmd -- $patterns
Nếu cmd
không hỗ trợ --
đánh dấu sự kết thúc của các tùy chọn, bạn nên báo cáo đó là một lỗi cho tác giả của nó (trừ khi đó là do lựa chọn và được ghi lại như tài liệu echo
).
Có những trường hợp ./*
giải quyết vấn đề --
không. Ví dụ:
awk -f file.awk -- *
thất bại nếu có một tệp được gọi a=b.txt
trong thư mục hiện tại (đặt biến awk a
thành b.txt
thay vì bảo nó xử lý tệp).
awk -f file.awk ./*
Không có vấn đề vì ./a
không phải là tên biến awk hợp lệ, do đó ./a=b.txt
không được coi là một phép gán biến.
cat -- * | wc -l
thất bại nếu có một tệp được gọi -
trong thư mục hiện tại, vì nó bảo cat
đọc từ stdin của nó ( -
đặc biệt đối với hầu hết các tiện ích xử lý văn bản và cd
/ pushd
).
cat ./* | wc -l
Không sao vì ./-
không đặc biệt cat
.
Những thứ như:
grep -l -- foo *.txt | wc -l
để đếm số lượng tệp chứa foo
sai vì nó giả sử tên tệp không chứa các ký tự dòng mới ( wc -l
đếm các ký tự dòng mới, các ký tự đầu ra grep
cho mỗi tệp và các tên trong chính tên tệp). Bạn nên sử dụng thay thế:
grep -l foo ./*.txt | grep -c /
(đếm số lượng /
ký tự đáng tin cậy hơn vì chỉ có thể có một ký tự cho mỗi tên tệp).
Đối với đệ quy grep
, thủ thuật tương đương là sử dụng:
grep -rl foo .//. | grep -c //
./*
có thể có một số tác dụng phụ không mong muốn mặc dù.
cat ./*
thêm hai ký tự cho mỗi tệp, do đó sẽ khiến bạn đạt đến giới hạn kích thước tối đa của đối số + môi trường sớm hơn. Và đôi khi bạn không muốn điều đó ./
được báo cáo trong đầu ra. Như:
grep foo ./*
Sẽ xuất:
./a.txt: foobar
thay vì:
a.txt: foobar
1 . Tôi cảm thấy như tôi phải mở rộng về điều đó ở đây, sau khi thảo luận trong các bình luận.
$ du -hs ./*
0 ./a
12 b
0 ./-c
0 ./foo
Ở trên, việc ./
đánh dấu phần đầu của mỗi tệp có nghĩa là chúng ta có thể xác định rõ ràng nơi mỗi tên tệp bắt đầu (tại ./
) và nơi kết thúc (tại dòng mới trước phần tiếp theo ./
hoặc phần cuối của đầu ra).
Điều đó có nghĩa là đầu ra của du ./*
, trái ngược với du -- *
) có thể được phân tích cú pháp một cách đáng tin cậy, mặc dù không dễ dàng như vậy trong một tập lệnh.
Khi đầu ra đi đến một thiết bị đầu cuối, có rất nhiều cách khác mà một tên tệp có thể đánh lừa bạn:
\r
di chuyển con trỏ đến đầu dòng, \b
di chuyển con trỏ trở lại, \e[C
chuyển tiếp (trong hầu hết các thiết bị đầu cuối) ...Có các ký tự Unicode trông giống như dấu gạch chéo trong hầu hết các phông chữ
$ printf '\u002f \u2044 \u2215 \u2571 \u29F8\n'
/ ⁄ ∕ ╱ ⧸
(xem cách nó đi trong trình duyệt của bạn).
Một ví dụ:
$ touch x 'x ' $'y\bx' $'x\n0\t.\u2215x' $'y\r0\t.\e[Cx'
$ ln x y
$ du -hs ./*
0 ./x
0 ./x
0 ./x
0 .∕x
0 ./x
0 ./x
Rất nhiều x
nhưng y
thiếu.
Một số công cụ như GNU
ls sẽ thay thế các ký tự không in được bằng dấu chấm hỏi (lưu ý rằng ∕
(U + 2215) có thể in được) khi đầu ra đi đến một thiết bị đầu cuối. GNU du
thì không.
Có nhiều cách để khiến họ tự tiết lộ:
$ ls
x x x?0?.∕x y y?0?.?[Cx y?x
$ LC_ALL=C ls
x x?0?.???x x y y?x y?0?.?[Cx
Xem cách ∕
chuyển sang ???
sau khi chúng tôi nói ls
rằng bộ ký tự của chúng tôi là ASCII.
$ du -hs ./* | LC_ALL=C sed -n l
0\t./x$
0\t./x $
0\t./x$
0\t.\342\210\225x$
0\t./y\r0\t.\033[Cx$
0\t./y\bx$
$
đánh dấu kết thúc của dòng, vì vậy chúng ta có thể phát hiện ra "x"
vs "x "
, tất cả các ký tự không in được và các ký tự không phải ASCII được biểu thị bằng một chuỗi dấu gạch chéo ngược (chính dấu gạch chéo ngược sẽ được biểu thị bằng hai dấu gạch chéo ngược) có nghĩa là nó không rõ ràng. Đó là GNU sed
, nó phải giống nhau trong tất cả các sed
triển khai tuân thủ POSIX nhưng lưu ý rằng một số sed
triển khai cũ gần như không hữu ích.
$ du -hs ./* | cat -vte
0^I./x$
0^I./x $
0^I./x$
0^I.M-bM-^HM-^Ux$
(không chuẩn nhưng khá phổ biến, cũng cat -A
có một số triển khai). Đó là một hữu ích và sử dụng một đại diện khác nhau nhưng không rõ ràng ( "^I"
và <TAB>
được hiển thị giống nhau chẳng hạn).
$ du -hs ./* | od -vtc
0000000 0 \t . / x \n 0 \t . / x \n 0 \t .
0000020 / x \n 0 \t . 342 210 225 x \n 0 \t . / y
0000040 \r 0 \t . 033 [ C x \n 0 \t . / y \b x
0000060 \n
0000061
Đó là một tiêu chuẩn và rõ ràng (và nhất quán từ thực hiện đến thực hiện) nhưng không dễ đọc.
Bạn sẽ nhận thấy rằng y
không bao giờ xuất hiện ở trên. Đó là một hoàn toàn không liên quan vấn đề với du -hs *
điều đó không có gì để làm với tên file nhưng cần lưu ý: vì du
sử dụng các báo cáo đĩa, nó không báo cáo các liên kết khác vào một tập tin đã được liệt kê (không phải tất cả du
hiện thực cư xử như thế mặc dù khi các liên kết cứng được liệt kê trên dòng lệnh).
[a-z0-9.+-]
.
/tmp
) hoặc khu vực có nhiều xe hơi đắt tiền ( $HOME
) và thậm chí còn tệ hơn khi đến trang web Hỏi & Đáp và nói rằng luôn luôn không khóa xe của bạn mà không chỉ định điều kiện nào (trong nhà để xe bị khóa, kịch bản bạn đã viết chỉ chạy một mình trên một máy không được kết nối với bất kỳ mạng hoặc bộ lưu trữ di động nào ...)
"b "
hoặc "a\bb"
) sẽ đánh lừa người dùng trên thiết bị đầu cuối nhưng không phải là một đoạn mã phân tích cú pháp đầu ra của du ./*
. Tôi có lẽ nên thêm một lưu ý về điều đó. Sẽ làm vào ngày mai. Lưu ý rằng trước đó tôi có nghĩa là đặc quyền theo nghĩa chung, không root
(mặc dù áp dụng tất cả nhiều hơn cho root
tất nhiên). dòng mới được cho phép, bỏ qua chúng là một lỗi. bọ có thói quen bị lợi dụng. Bạn phải đo lường rủi ro theo từng trường hợp. Thực hành mã hóa tốt có thể tránh được các vấn đề trong nhiều trường hợp. Chắc chắn trên SE, chúng ta nên nâng cao nhận thức.
Không có sự khác biệt giữa a *
và ./*
về những gì tập tin sẽ liệt kê. Sự khác biệt duy nhất sẽ là với hình thức thứ 2, mỗi tệp sẽ có một dấu gạch chéo có ./
tiền tố ở phía trước chúng, thường có nghĩa là thư mục hiện tại.
Hãy nhớ rằng .
thư mục là một ký hiệu viết tắt cho thư mục hiện tại.
$ ls -la | head -4
total 28864
drwx------. 104 saml saml 12288 Jan 23 20:04 .
drwxr-xr-x. 4 root root 4096 Jul 8 2013 ..
-rw-rw-r--. 1 saml saml 972 Oct 6 20:26 abcdefg
Bạn có thể thuyết phục bản thân rằng 2 danh sách này về cơ bản là giống nhau bằng cách sử dụng echo
để xem phần vỏ sẽ mở rộng chúng thành gì.
$ echo *
$ echo ./*
2 lệnh này sẽ liệt kê tất cả các tệp trong thư mục hiện tại của bạn.
Chúng tôi có thể tạo một số dữ liệu giả như vậy:
$ touch file{1..5}
$ ll
total 0
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file1
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file2
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file3
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file4
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file5
Bây giờ khi chúng ta sử dụng các echo
lệnh trên, chúng ta thấy đầu ra sau:
$ echo *
file1 file2 file3 file4 file5
$ echo ./*
./file1 ./file2 ./file3 ./file4 ./file5
Sự khác biệt này có vẻ không cần thiết nhưng có những tình huống bạn muốn đảm bảo với các công cụ dòng lệnh Unix khác nhau mà bạn đang truyền tên tệp cho chúng thông qua dòng lệnh, và không có gì nữa!
Như câu trả lời của @ Stephane chỉ ra , do bản chất của các ký tự là hợp pháp khi đặt tên tệp và thư mục trong Unix, tên tệp nguy hiểm có thể được tạo ra có tác dụng phụ không mong muốn khi chúng được chuyển đến các lệnh Unix khác nhau trong dòng lệnh.
Vì vậy, thường việc sử dụng ./
sẽ được sử dụng để giúp đảm bảo rằng tên tệp mở rộng được coi là tên tệp khi được chuyển làm đối số cho các lệnh Unix khác nhau.