Tôi luôn thấy các câu trả lời trích dẫn liên kết này một cách dứt khoát "Đừng phân tích cú pháp ls
!" Điều này làm phiền tôi vì một vài lý do:
Có vẻ như thông tin trong liên kết đó đã được chấp nhận bán buôn với rất ít câu hỏi, mặc dù tôi có thể chọn ra ít nhất một vài lỗi trong việc đọc thông thường.
Có vẻ như các vấn đề được nêu trong liên kết đó đã gây ra mong muốn tìm ra giải pháp.
Từ đoạn đầu tiên:
... Khi bạn hỏi
[ls]
danh sách các tệp, có một vấn đề rất lớn: Unix cho phép hầu hết mọi ký tự trong tên tệp, bao gồm khoảng trắng, dòng mới, dấu phẩy, ký hiệu ống và hầu hết mọi thứ khác mà bạn từng thử sử dụng như một dấu phân cách trừ NUL. ...ls
tách tên tập tin với dòng mới. Điều này là tốt cho đến khi bạn có một tập tin với một dòng mới trong tên của nó. Và vì tôi không biết về bất kỳ triển khails
nào cho phép bạn chấm dứt tên tệp bằng các ký tự NUL thay vì dòng mới, điều này khiến chúng tôi không thể có được danh sách tên tệp một cách an toànls
.
Bummer, phải không? Bao giờ chúng ta có thể xử lý một tập dữ liệu được liệt kê chấm dứt cho dữ liệu có thể chứa dòng mới? Chà, nếu những người trả lời các câu hỏi trên trang web này không làm điều này hàng ngày, tôi có thể nghĩ rằng chúng tôi đã gặp rắc rối.
Mặc dù vậy, sự thật là hầu hết các ls
triển khai thực sự cung cấp một api rất đơn giản để phân tích cú pháp đầu ra của chúng và tất cả chúng ta đã làm tất cả cùng mà không hề nhận ra. Bạn không chỉ có thể kết thúc một tên tệp bằng null, bạn có thể bắt đầu một tên bằng null hoặc với bất kỳ chuỗi tùy ý nào khác mà bạn có thể mong muốn. Hơn thế nữa, bạn có thể gán các chuỗi tùy ý cho mỗi loại tệp . Xin vui lòng xem xét:
LS_COLORS='lc=\0:rc=:ec=\0\0\0:fi=:di=:' ls -l --color=always | cat -A
total 4$
drwxr-xr-x 1 mikeserv mikeserv 0 Jul 10 01:05 ^@^@^@^@dir^@^@^@/$
-rw-r--r-- 1 mikeserv mikeserv 4 Jul 10 02:18 ^@file1^@^@^@$
-rw-r--r-- 1 mikeserv mikeserv 0 Jul 10 01:08 ^@file2^@^@^@$
-rw-r--r-- 1 mikeserv mikeserv 0 Jul 10 02:27 ^@new$
line$
file^@^@^@$
^@
Xem điều này để biết thêm.
Bây giờ đây là phần tiếp theo của bài viết này thực sự khiến tôi hiểu:
$ ls -l
total 8
-rw-r----- 1 lhunath lhunath 19 Mar 27 10:47 a
-rw-r----- 1 lhunath lhunath 0 Mar 27 10:47 a?newline
-rw-r----- 1 lhunath lhunath 0 Mar 27 10:47 a space
Vấn đề là từ đầu ra của
ls
, cả bạn và máy tính đều không thể biết được phần nào của nó tạo thành tên tệp. Có phải mỗi từ? Không. Có phải mỗi dòng? Không. Không có câu trả lời chính xác cho câu hỏi này ngoài: bạn không thể nói.Ngoài ra, hãy chú ý cách
ls
đôi khi cắt xén dữ liệu tên tệp của bạn (trong trường hợp của chúng tôi, nó đã biến\n
ký tự ở giữa các từ "a" và "dòng mới" thành một dấu hỏi ......
Nếu bạn chỉ muốn lặp lại tất cả các tệp trong thư mục hiện tại, hãy sử dụng
for
vòng lặp và toàn cầu:
for f in *; do
[[ -e $f ]] || continue
...
done
Tác giả gọi nó là tên tệp bị cắt xén khi ls
trả về một danh sách tên tệp có chứa các khối vỏ và sau đó khuyến nghị sử dụng toàn cầu shell để lấy danh sách tệp!
Hãy xem xét những điều sau đây:
printf 'touch ./"%b"\n' "file\nname" "f i l e n a m e" |
. /dev/stdin
ls -1q
f i l e n a m e
file?name
IFS="
" ; printf "'%s'\n" $(ls -1q)
'f i l e n a m e'
'file
name'
POSIX định nghĩa các -1
và -q
ls
toán hạng như vậy:
-q
- Buộc từng phiên bản của các ký tự tên tệp không thể in và<tab>
s được viết dưới dạng'?'
ký tự dấu hỏi ( ). Việc triển khai có thể cung cấp tùy chọn này theo mặc định nếu đầu ra là cho thiết bị đầu cuối.
-1
- (Chữ số một.) Buộc đầu ra là một mục nhập trên mỗi dòng.
Globbing không phải là không có vấn đề riêng của nó - ?
khớp bất kỳ ký tự nào để nhiều ?
kết quả khớp trong danh sách sẽ khớp với cùng một tệp nhiều lần. Điều đó dễ dàng xử lý.
Mặc dù làm thế nào để làm điều này không phải là vấn đề - nó không mất nhiều thời gian để làm và được trình bày dưới đây - tôi đã quan tâm tại sao không . Khi tôi xem xét nó, câu trả lời tốt nhất cho câu hỏi đó đã được chấp nhận. Tôi sẽ đề nghị bạn cố gắng tập trung thường xuyên hơn vào việc nói với mọi người những gì họ có thể làm hơn là những gì họ không thể. Theo tôi nghĩ, bạn ít có khả năng bị chứng minh là sai.
Nhưng tại sao lại phải thử? Phải thừa nhận rằng, động lực chính của tôi là những người khác cứ nói với tôi rằng tôi không thể. Tôi biết rất rõ rằng ls
đầu ra là thường xuyên và có thể dự đoán được như bạn có thể muốn nó miễn là bạn biết những gì cần tìm kiếm. Thông tin sai làm phiền tôi nhiều hơn là làm hầu hết mọi thứ.
Mặc dù vậy, sự thật là ngoại trừ đáng chú ý của cả Patrick và Wumpus Q. Câu trả lời của Wumbley (mặc dù xử lý tuyệt vời sau này) , tôi coi hầu hết các thông tin trong các câu trả lời ở đây là chính xác - một vỏ toàn cầu đơn giản hơn để sử dụng và thường hiệu quả hơn khi tìm kiếm thư mục hiện tại hơn là phân tích cú pháp ls
. Tuy nhiên, ít nhất họ không phải là lý do để biện minh cho việc tuyên truyền thông tin sai lệch được trích dẫn trong bài viết trên đây và họ cũng không thể chấp nhận biện minh cho " không bao giờ phân tíchls
" .
Xin lưu ý rằng kết quả không nhất quán của câu trả lời của Patrick chủ yếu là kết quả của việc anh ta sử dụng zsh
sau đó bash
. zsh
- theo mặc định - không $(
thay thế lệnh chia từ thay thế )
theo cách di động. Vì vậy, khi anh hỏi phần còn lại của các tập tin đi đâu? Câu trả lời cho câu hỏi đó là vỏ của bạn đã ăn chúng. Đây là lý do tại sao bạn cần đặt SH_WORD_SPLIT
biến khi sử dụng zsh
và xử lý mã shell di động. Tôi coi việc anh ấy không lưu ý điều này trong câu trả lời của anh ấy là hết sức sai lệch.
Câu trả lời của Wumpus không tính toán cho tôi - trong ngữ cảnh danh sách, ?
nhân vật là một quả địa cầu. Tôi không biết làm thế nào khác để nói điều đó.
Để xử lý nhiều trường hợp kết quả, bạn cần hạn chế sự tham lam của toàn cầu. Sau đây sẽ chỉ tạo một cơ sở thử nghiệm các tên tệp khủng khiếp và hiển thị nó cho bạn:
{ printf %b $(printf \\%04o `seq 0 127`) |
sed "/[^[-b]*/s///g
s/\(.\)\(.\)/touch '?\v\2' '\1\t\2' '\1\n\2'\n/g" |
. /dev/stdin
echo '`ls` ?QUOTED `-m` COMMA,SEP'
ls -qm
echo ; echo 'NOW LITERAL - COMMA,SEP'
ls -m | cat
( set -- * ; printf "\nFILE COUNT: %s\n" $# )
}
ĐẦU RA
`ls` ?QUOTED `-m` COMMA,SEP
??\, ??^, ??`, ??b, [?\, [?\, ]?^, ]?^, _?`, _?`, a?b, a?b
NOW LITERAL - COMMA,SEP
?
\, ?
^, ?
`, ?
b, [ \, [
\, ] ^, ]
^, _ `, _
`, a b, a
b
FILE COUNT: 12
Bây giờ tôi sẽ an toàn mỗi nhân vật đó không phải là một /slash
, -dash
, :colon
, hoặc ký tự chữ-số trong một glob vỏ sau đó sort -u
danh sách cho kết quả tuyệt vời. Điều này là an toàn vì ls
đã loại bỏ mọi ký tự không in được cho chúng tôi. Đồng hồ đeo tay:
for f in $(
ls -1q |
sed 's|[^-:/[:alnum:]]|[!-\\:[:alnum:]]|g' |
sort -u | {
echo 'PRE-GLOB:' >&2
tee /dev/fd/2
printf '\nPOST-GLOB:\n' >&2
}
) ; do
printf "FILE #$((i=i+1)): '%s'\n" "$f"
done
ĐẦU RA:
PRE-GLOB:
[!-\:[:alnum:]][!-\:[:alnum:]][!-\:[:alnum:]]
[!-\:[:alnum:]][!-\:[:alnum:]]b
a[!-\:[:alnum:]]b
POST-GLOB:
FILE #1: '?
\'
FILE #2: '?
^'
FILE #3: '?
`'
FILE #4: '[ \'
FILE #5: '[
\'
FILE #6: '] ^'
FILE #7: ']
^'
FILE #8: '_ `'
FILE #9: '_
`'
FILE #10: '?
b'
FILE #11: 'a b'
FILE #12: 'a
b'
Dưới đây tôi tiếp cận vấn đề một lần nữa nhưng tôi sử dụng một phương pháp khác. Hãy nhớ rằng - ngoài \0
null - /
ký tự ASCII là byte duy nhất bị cấm trong tên đường dẫn. Tôi đặt các khối u qua một bên ở đây và thay vào đó kết hợp -d
tùy chọn được chỉ định POSIX ls
và -exec $cmd {} +
cấu trúc được chỉ định POSIX cho find
. Bởi vì find
sẽ chỉ tự nhiên phát ra một /
thứ tự theo trình tự, sau đây dễ dàng tạo ra một filelist đệ quy và đáng tin cậy bao gồm tất cả các thông tin nha khoa cho mỗi mục. Chỉ cần tưởng tượng những gì bạn có thể làm với một cái gì đó như thế này:
#v#note: to do this fully portably substitute an actual newline \#v#
#v#for 'n' for the first sed invocation#v#
cd ..
find ././ -exec ls -1ldin {} + |
sed -e '\| *\./\./|{s||\n.///|;i///' -e \} |
sed 'N;s|\(\n\)///|///\1|;$s|$|///|;P;D'
###OUTPUT
152398 drwxr-xr-x 1 1000 1000 72 Jun 24 14:49
.///testls///
152399 -rw-r--r-- 1 1000 1000 0 Jun 24 14:49
.///testls/?
\///
152402 -rw-r--r-- 1 1000 1000 0 Jun 24 14:49
.///testls/?
^///
152405 -rw-r--r-- 1 1000 1000 0 Jun 24 14:49
.///testls/?
`///
...
ls -i
có thể rất hữu ích - đặc biệt là khi tính duy nhất của kết quả được đề cập.
ls -1iq |
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' |
tr -d '\n' |
xargs find
Đây chỉ là những phương tiện di động nhất mà tôi có thể nghĩ ra. Với GNU ls
bạn có thể làm:
ls --quoting-style=WORD
Và cuối cùng, đây là một phương pháp phân tích cú phápls
đơn giản hơn nhiều mà tôi tình cờ sử dụng khá thường xuyên khi cần số inode:
ls -1iq | grep -o '^ *[0-9]*'
Điều đó chỉ trả về số inode - đó là một tùy chọn POSIX tiện dụng khác.
stat
trong câu trả lời của mình, vì nó thực sự kiểm tra xem mỗi tệp có tồn tại không. Bit của bạn ở phía dưới với sed
điều không hoạt động.
ls
ngay từ đầu? Những gì bạn đang mô tả là rất khó. Tôi sẽ cần giải cấu trúc nó để hiểu tất cả về nó và tôi là một người dùng tương đối có năng lực. Bạn có thể không thể mong đợi Joe trung bình của bạn có thể đối phó với một cái gì đó như thế này.
ls
đầu ra là sai được bao phủ tốt trong liên kết ban đầu (và ở nhiều nơi khác). Câu hỏi này sẽ hợp lý nếu OP yêu cầu trợ giúp để hiểu nó, nhưng thay vào đó OP chỉ đơn giản là cố gắng chứng minh việc sử dụng không chính xác của mình là ổn.
parsing ls is bad
. Làm for something in $(command)
và dựa vào phân tách từ để có kết quả chính xác là điều tồi tệ đối với phần lớn trong số command's
đó không có đầu ra đơn giản.
time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'
= 3,18 giây so vớitime bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'
= 1,28s