Tại sao * không * phân tích `ls` (và phải làm gì thay thế)?


204

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:

  1. 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.

  2. 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. ... lstá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 khai lsnà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àn ls.

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 lstriể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 \nký tự ở giữa các từ "a""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 forvò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 lstrả 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-q lstoá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 zshsau đó 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_SPLITbiến khi sử dụng zshvà 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 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 -udanh 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 \0null - /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 -dtùy chọn được chỉ định POSIX ls-exec $cmd {} +cấu trúc được chỉ định POSIX cho find. Bởi vì findsẽ 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 lsbạ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.


12
@mikeerv Ok tôi đã làm. Shell toàn cầu nhanh hơn 2,48 lần. time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'= 3,18 giây so với time bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'= 1,28s
Patrick

28
Liên quan đến bản cập nhật gần đây nhất của bạn, vui lòng ngừng dựa vào đầu ra trực quan khi xác định rằng mã của bạn hoạt động. Truyền đầu ra của bạn cho một chương trình thực tế và để chương trình thử và thực hiện một thao tác trên tệp. Đây là lý do tại sao tôi sử dụng stattrong 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.
Patrick

57
Bạn không thể nghiêm túc. Làm thế nào có thể nhảy qua tất cả các vòng mà câu hỏi của bạn mô tả dễ dàng hơn hoặc đơn giản hơn hoặc bằng mọi cách tốt hơn là không phân tích cú pháp lsngay 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.
terdon

46
-1 để sử dụng một câu hỏi để chọn một đối số. Tất cả các lý do phân tích 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.
R ..

14
@mikeerv Không chỉ có thế 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.
BroSlow

Câu trả lời:


184

Tôi hoàn toàn không tin vào điều này, nhưng hãy giả sử vì lý do mà bạn có thể , nếu bạn chuẩn bị nỗ lực, phân tích kết quả lsmột cách đáng tin cậy, ngay cả khi đối mặt với "kẻ thù" - một người biết mã bạn đã viết và cố tình chọn tên tệp được thiết kế để phá vỡ nó.

Ngay cả khi bạn có thể làm điều đó, nó vẫn sẽ là một ý tưởng tồi .

Vỏ Bourne không phải là một ngôn ngữ tốt. Nó không nên được sử dụng cho bất cứ điều gì phức tạp, trừ khi tính di động cực kỳ quan trọng hơn bất kỳ yếu tố nào khác (ví dụ autoconf).

Tôi khẳng định rằng nếu bạn gặp phải một vấn đề trong đó phân tích cú pháp đầu ra lscó vẻ giống như đường dẫn ít kháng cự nhất đối với tập lệnh shell, thì đó là một dấu hiệu mạnh mẽ cho thấy bất cứ điều gì bạn đang làm là quá phức tạp đối với shell và bạn nên viết lại toàn bộ nội dung Perl hoặc Python. Đây là chương trình cuối cùng của bạn trong Python:

import os, sys
for subdir, dirs, files in os.walk("."):
    for f in dirs + files:
      ino = os.lstat(os.path.join(subdir, f)).st_ino
      sys.stdout.write("%d %s %s\n" % (ino, subdir, f))

Điều này không có vấn đề gì với các ký tự bất thường trong tên tệp - đầu ra không rõ ràng giống như đầu ra của lsmơ hồ, nhưng điều đó sẽ không quan trọng trong một chương trình "thực" (trái ngược với bản demo như thế này), điều này sẽ sử dụng kết quả os.path.join(subdir, f)trực tiếp.

Quan trọng không kém, và trái ngược hoàn toàn với những gì bạn đã viết, nó vẫn sẽ có ý nghĩa sáu tháng kể từ bây giờ, và nó sẽ dễ dàng sửa đổi khi bạn cần nó để làm một cái gì đó hơi khác. Bằng cách minh họa, giả sử bạn phát hiện ra nhu cầu loại trừ dotfiles và sao lưu trình chỉnh sửa và xử lý mọi thứ theo thứ tự bảng chữ cái theo tên cơ sở:

import os, sys
filelist = []
for subdir, dirs, files in os.walk("."):
    for f in dirs + files:
        if f[0] == '.' or f[-1] == '~': continue
        lstat = os.lstat(os.path.join(subdir, f))
        filelist.append((f, subdir, lstat.st_ino))

filelist.sort(key = lambda x: x[0])
for f, subdir, ino in filelist: 
   sys.stdout.write("%d %s %s\n" % (ino, subdir, f))

5
Điều này là tốt Điều đó for in | for innói lên sự đệ quy? Tôi không chắc. Ngay cả khi nó không thể là nhiều hơn một, phải không? Đây là câu trả lời duy nhất có ý nghĩa với tôi cho đến nay.
mikeerv

10
Không có đệ quy, chỉ lồng nhau for. os.walkđang thực hiện một số công việc nặng nề phía sau hậu trường, nhưng bạn không phải lo lắng về điều đó nhiều hơn là bạn phải lo lắng về cách thức lshoặc findlàm việc nội bộ.
zwol

6
Về mặt kỹ thuật, os.walktrả về một đối tượng máy phát . Trình tạo là phiên bản danh sách lười biếng của Python. Mỗi khi vòng lặp for-loop lặp bên ngoài, trình tạo được gọi và "mang lại" nội dung của thư mục con khác. Chức năng tương đương trong Perl là File::Find, nếu điều đó giúp.
zwol

6
Bạn nên biết rằng tôi đồng ý 100% với tài liệu bạn đang chỉ trích và với câu trả lời của Patrick và Terdon. Câu trả lời của tôi nhằm cung cấp một lý do độc lập, bổ sung để tránh phân tích cú pháp lsđầu ra.
zwol

19
Điều này rất sai lệch. Shell không phải là ngôn ngữ lập trình tốt, nhưng chỉ vì nó không phải là ngôn ngữ lập trình. Đó là một ngôn ngữ kịch bản. Và đó là một ngôn ngữ kịch bản tốt.
Miles Rout

178

Liên kết đó được tham chiếu rất nhiều vì thông tin hoàn toàn chính xác, và nó đã tồn tại trong một thời gian rất dài.


lsthay thế các ký tự không in được bằng các ký tự toàn cầu có, nhưng các ký tự đó không có trong tên tệp thực tế. Vì sao vấn đề này? 2 lý do:

  1. Nếu bạn chuyển tên tệp đó cho một chương trình, tên tệp đó không thực sự tồn tại. Nó sẽ phải mở rộng toàn cầu để có được tên tập tin thực sự.
  2. Tập tin toàn cầu có thể khớp với nhiều hơn một tập tin.

Ví dụ:

$ touch a$'\t'b
$ touch a$'\n'b
$ ls -1
a?b
a?b

Lưu ý cách chúng ta có 2 tệp trông giống hệt nhau. Làm thế nào bạn sẽ phân biệt chúng nếu cả hai được đại diện là a?b?


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 shell shell để lấy danh sách tệp!

Có một sự khác biệt ở đây. Khi bạn lấy lại một quả địa cầu, như được hiển thị, quả cầu đó có thể khớp với nhiều hơn một tệp. Tuy nhiên, khi bạn lặp lại qua các kết quả khớp với một quả địa cầu, bạn sẽ lấy lại được tệp chính xác, không phải là một quả cầu.

Ví dụ:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Lưu ý cách xxdđầu ra cho thấy có $filechứa các ký tự thô \t\n, không ?.

Nếu bạn sử dụng ls, bạn nhận được điều này thay thế:

for file in $(ls -1q); do printf '%s' "$file" | xxd; done
0000000: 613f 62                                  a?b
0000000: 613f 62                                  a?b

"Dù sao tôi cũng sẽ lặp đi lặp lại, tại sao không sử dụng ls?"

Ví dụ của bạn bạn đã không thực sự làm việc. Có vẻ như nó hoạt động, nhưng nó không.

Tôi đang đề cập đến điều này:

 for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done

Tôi đã tạo một thư mục với một loạt các tên tệp:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6120 62                                  a b
0000000: 6120 2062                                a  b
0000000: 61e2 8082 62                             a...b
0000000: 61e2 8083 62                             a...b
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Khi tôi chạy mã của bạn, tôi nhận được điều này:

$ for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done
./ab
./ab

Phần còn lại của các tập tin đi đâu?

Thay vào đó, hãy thử điều này:

$ for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f"; done
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a??b’: No such file or directory
./ab
./ab
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a?b’: No such file or directory

Bây giờ hãy sử dụng một quả cầu thực tế:

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./ab
./ab
./a b
./a
b

Với bash

Ví dụ trên là với vỏ bình thường của tôi, zsh. Khi tôi lặp lại quy trình với bash, tôi nhận được một tập kết quả hoàn toàn khác với ví dụ của bạn:

Cùng một tập tin:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6120 62                                  a b
0000000: 6120 2062                                a  b
0000000: 61e2 8082 62                             a...b
0000000: 61e2 8083 62                             a...b
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Kết quả hoàn toàn khác với mã của bạn:

for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f"; done
./a b
./ab
./ab
./a b
./a
b
./a  b
./ab
./ab
./a b
./ab
./ab
./a b
./a
b
./a b
./ab
./ab
./a b
./a
b

Với vỏ toàn cầu, nó hoạt động hoàn toàn tốt:

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./ab
./ab
./a b
./a
b

Lý do bash hành xử theo cách này quay trở lại một trong những điểm tôi đã đưa ra ở đầu câu trả lời: "Tập tin toàn cầu có thể khớp với nhiều hơn một tập tin".

lsđang trả lại cùng một global ( a?b) cho một số tệp, vì vậy mỗi lần chúng tôi mở rộng toàn cầu này, chúng tôi sẽ nhận được mọi tệp phù hợp với nó.


Cách tạo lại danh sách các tập tin tôi đang sử dụng:

touch 'a b' 'a  b' a$'\xe2\x80\x82'b a$'\xe2\x80\x83'b a$'\t'b a$'\n'b

Các mã hex là các ký tự NBSP UTF-8.


5
@mikeerv thực sự giải pháp của anh ấy không trả lại một quả địa cầu. Tôi chỉ cập nhật câu trả lời của tôi để làm rõ điểm đó.
Patrick

18
"Không phải phần còn lại"? Đó là hành vi không nhất quán và kết quả bất ngờ, làm thế nào mà không phải là một lý do?
Patrick

11
@mikeerv Bạn không thấy bình luận của tôi về câu hỏi của bạn? Shell globalbing nhanh hơn 2,5 lần so với ls. Tôi cũng yêu cầu bạn kiểm tra mã của mình vì nó không hoạt động. Zsh phải làm gì với điều này?
Patrick

27
@mikeerv Không, tất cả vẫn áp dụng ngay cả đối với bash. Mặc dù tôi đã hoàn thành câu hỏi này vì bạn không nghe những gì tôi nói.
Patrick

7
Bạn biết những gì, tôi nghĩ rằng tôi sẽ đưa ra câu trả lời này và làm rõ trong tôi rằng tôi đồng ý với tất cả những gì nó nói. ;-)
zwol

54

Hãy thử và đơn giản hóa một chút:

$ touch a$'\n'b a$'\t'b 'a b'
$ ls
a b  a?b  a?b
$ IFS="
"
$ set -- $(ls -1q | uniq)
$ echo "Total files in shell array: $#"
Total files in shell array: 4

Xem? Điều đó đã sai ngay tại đó. Có 3 tệp nhưng bash đang báo cáo 4. Điều này là do setđang được cung cấp các khối được tạo bởi lsđược mở rộng bởi trình bao trước khi được chuyển đến set. Đó là lý do tại sao bạn nhận được:

$ for x ; do
>     printf 'File #%d: %s\n' $((i=$i+1)) "$x"
> done
File #1: a b
File #2: a b
File #3: a    b
File #4: a
b

Hoặc, nếu bạn thích:

$ printf ./%s\\0 "$@" |
> od -A n -c -w1 |
> sed -n '/ \{1,3\}/s///;H
> /\\0/{g;s///;s/\n//gp;s/.*//;h}'
./a b
./a b
./a\tb
./a\nb

Ở trên đã được chạy trên bash 4.2.45.


2
Tôi ủng hộ điều này. Thật tốt khi thấy mã của riêng bạn cắn bạn. Nhưng chỉ vì tôi hiểu sai không có nghĩa là nó không thể được thực hiện đúng. Tôi đã chỉ cho bạn một cách rất đơn giản để thực hiện sáng nay ls -1qRi | grep -o '^ *[0-9]*'- đó là phân tích cú pháp lsđầu ra, anh bạn, và đó là cách nhanh nhất và tốt nhất mà tôi biết để có được danh sách các số inode.
mikeerv

38
@mikeerv: Nó có thể được thực hiện đúng, nếu bạn có thời gian và kiên nhẫn. Nhưng thực tế là, nó vốn dễ bị lỗi. Chính bạn đã hiểu sai. trong khi tranh luận về giá trị của nó! Đó là một cuộc tấn công lớn chống lại nó, nếu ngay cả một người chiến đấu vì nó không làm điều đó một cách chính xác. Và rất có thể, có lẽ bạn sẽ dành nhiều thời gian hơn để hiểu sai trước khi bạn hiểu đúng. Tôi không biết gì về bạn, nhưng hầu hết mọi người có liên quan đến thời gian của họ tốt hơn là tìm hiểu về những lứa tuổi có cùng dòng mã.
cHao

@cHao - tôi không tranh luận về công trạng của nó - tôi đã phản đối tuyên truyền của nó.
mikeerv

16
@mikeerv: Những lý lẽ chống lại nó là có cơ sở và rất xứng đáng. Ngay cả bạn đã cho họ thấy là đúng.
cHao

1
@cHao - tôi không đồng ý. Có một ranh giới không mấy tốt đẹp giữa một câu thần chú và một trí tuệ.
mikeerv

50

Đầu ra của ls -qkhông phải là một toàn cầu. Nó dùng ?để chỉ "Có một ký tự ở đây không thể hiển thị trực tiếp". Globs sử dụng ?có nghĩa là "Bất kỳ nhân vật được phép ở đây".

Quả cầu có các ký tự đặc biệt khác ( *[]ít nhất, và bên trong []cặp có nhiều hơn). Không ai trong số họ được trốn thoát bởi ls -q.

$ touch x '[x]'
$ ls -1q
[x]
x

Nếu bạn đối xử với ls -1qđầu ra, có một tập hợp các khối và mở rộng chúng, không chỉ bạn sẽ nhận được xhai lần, bạn sẽ [x]hoàn toàn bỏ lỡ . Là một quả địa cầu, nó không khớp với nhau như một chuỗi.

ls -q có nghĩa là để cứu mắt và / hoặc thiết bị đầu cuối của bạn khỏi các nhân vật điên rồ, không tạo ra thứ gì đó mà bạn có thể phản hồi lại vỏ.


42

Câu trả lời rất đơn giản: Các trường hợp đặc biệt của lsbạn phải xử lý lớn hơn bất kỳ lợi ích có thể có. Những trường hợp đặc biệt này có thể tránh được nếu bạn không phân tích cú pháp lsđầu ra.

Câu thần chú ở đây là không bao giờ tin tưởng vào hệ thống tập tin người dùng (tương đương với không bao giờ tin tưởng đầu vào của người dùng ). Nếu có một phương pháp sẽ luôn hoạt động, với độ chắc chắn 100%, thì đó sẽ là phương pháp bạn thích ngay cả khi lsthực hiện tương tự nhưng ít chắc chắn hơn. Tôi sẽ không đi sâu vào chi tiết kỹ thuật vì chúng được bao phủ bởi terdonPatrick . Tôi biết rằng do những rủi ro khi sử dụng lstrong một giao dịch quan trọng (và có thể tốn kém) khi công việc / uy tín của tôi nằm trên đường dây, tôi sẽ thích bất kỳ giải pháp nào không có mức độ không chắc chắn nếu có thể tránh được.

Tôi biết một số người thích một số rủi ro hơn sự chắc chắn , nhưng tôi đã nộp báo cáo lỗi .


33

Lý do mọi người nói không bao giờ làm điều gì đó không nhất thiết là vì nó hoàn toàn tích cực không thể được thực hiện một cách chính xác. Chúng tôi có thể làm như vậy, nhưng nó có thể phức tạp hơn, kém hiệu quả hơn cả về không gian hoặc thời gian. Ví dụ, sẽ rất hoàn hảo khi nói "Không bao giờ xây dựng một phụ trợ thương mại điện tử lớn trong lắp ráp x86".

Vì vậy, bây giờ đến vấn đề hiện tại: Như bạn đã chứng minh, bạn có thể tạo một giải pháp phân tích cú pháp ls và đưa ra kết quả đúng - vì vậy tính chính xác không phải là vấn đề.

Nó phức tạp hơn? Có, nhưng chúng ta có thể che giấu điều đó đằng sau một chức năng của người trợ giúp.

Vì vậy, bây giờ để hiệu quả:

Hiệu quả về không gian: Giải pháp của bạn dựa vào uniqđể lọc ra các bản sao, do đó chúng tôi không thể tạo ra kết quả một cách lười biếng. Vì vậy, hoặc O(1)so với O(n)hoặc cả hai đều có O(n).

Hiệu quả về thời gian: Trường hợp tốt nhất uniqsử dụng cách tiếp cận hashmap để chúng tôi vẫn có O(n)thuật toán về số lượng phần tử được mua , có lẽ là vậy O(n log n).

Bây giờ vấn đề thực sự: Trong khi thuật toán của bạn vẫn không quá tệ, tôi thực sự cẩn thận khi sử dụng các yếu tố được mua và không phải là các yếu tố cho n. Bởi vì điều đó làm cho một sự khác biệt lớn. Giả sử bạn có một tệp \n\nsẽ dẫn đến một quả địa cầu ??để khớp với mỗi tệp 2 ký tự trong danh sách. Vui thay nếu bạn có một tệp khác \n\rcũng sẽ dẫn đến ??và cũng trả lại tất cả 2 tệp ký tự .. xem điều này sẽ đi đâu? Hàm mũ thay vì hành vi tuyến tính chắc chắn đủ điều kiện là "hành vi thời gian chạy tồi tệ hơn" .. đó là sự khác biệt giữa thuật toán thực tế và thuật toán bạn viết trong các tạp chí CS lý thuyết về.

Mọi người đều thích những ví dụ phải không? Ở đây chúng tôi đi. Tạo một thư mục có tên "test" và sử dụng tập lệnh python này trong cùng thư mục chứa thư mục.

#!/usr/bin/env python3
import itertools
dir = "test/"
filename_length = 3
options = "\a\b\t\n\v\f\r"

for filename in itertools.product(options, repeat=filename_length):
        open(dir + ''.join(filename), "a").close()

Chỉ có điều điều này là tạo ra tất cả các sản phẩm có độ dài 3 cho 7 ký tự. Toán trung học cho chúng ta biết rằng phải là 343 tệp. Vâng, nó phải được in rất nhanh, vì vậy hãy xem:

time for f in *; do stat --format='%n' "./$f" >/dev/null; done
real    0m0.508s
user    0m0.051s
sys 0m0.480s

Bây giờ hãy thử giải pháp đầu tiên của bạn, bởi vì tôi thực sự không thể có được điều này

eval set -- $(ls -1qrR ././ | tr ' ' '?' |
sed -e '\|^\(\.\{,1\}\)/\.\(/.*\):|{' -e \
        's//\1\2/;\|/$|!s|.*|&/|;h;s/.*//;b}' -e \
        '/..*/!d;G;s/\(.*\)\n\(.*\)/\2\1/' -e \
        "s/'/'\\\''/g;s/.*/'&'/;s/?/'[\"?\$IFS\"]'/g" |
uniq)

điều ở đây để làm việc trên Linux mint 16 (mà tôi nghĩ đã nói lên rất nhiều về tính khả dụng của phương pháp này).

Dù sao đi nữa vì phần lớn ở trên chỉ lọc kết quả sau khi nhận được nó, giải pháp trước đó ít nhất phải nhanh như sau (không có thủ thuật inode nào trong đó - nhưng những điều đó không đáng tin cậy nên bạn sẽ từ bỏ tính chính xác).

Vậy bây giờ là bao lâu

time for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f" >/dev/null; done

lấy? Chà, tôi thực sự không biết, phải mất một thời gian để kiểm tra 343 ^ 343 tên tệp - Tôi sẽ nói với bạn sau cái chết nóng của vũ trụ.


6
Tất nhiên, như được đề cập trong các bình luận dưới một câu trả lời khác , tuyên bố "... bạn đã chứng minh rằng bạn có thể tạo ra một giải pháp phân tích ls và đưa ra kết quả đúng ..." thực sự không đúng.
tự đại diện

26

Ý định được nêu của OP

lời nói đầu và câu trả lời ban đầu của lý cập nhật trên 2015/05/18

mikeerv (OP) đã nêu trong bản cập nhật mới nhất cho câu hỏi của mình: "Tôi thực sự coi đó là một sự xấu hổ mặc dù lần đầu tiên tôi đã hỏi câu hỏi này để chỉ ra một nguồn thông tin sai lệch, và thật không may, câu trả lời được đánh giá cao nhất ở đây là phần lớn gây hiểu lầm. "

Vâng, được thôi; Tôi cảm thấy thật xấu hổ khi tôi đã dành quá nhiều thời gian để cố gắng tìm ra cách giải thích ý nghĩa của mình chỉ để thấy điều đó khi tôi đọc lại câu hỏi. Câu hỏi này đã kết thúc "[phát] thảo luận chứ không phải là câu trả lời" và kết thúc nặng trong lúc ~ 18K của văn bản (đối với câu hỏi mình, chỉ để được rõ ràng) đó sẽ là dài ngay cả đối với một bài đăng blog.

Nhưng StackExchange không phải là hộp xà phòng của bạn và nó không phải là blog của bạn. Tuy nhiên, trong thực tế, bạn đã sử dụng nó như ít nhất là cả hai. Mọi người cuối cùng đã dành rất nhiều thời gian để trả lời "Chỉ ra" của bạn thay vì trả lời các câu hỏi thực tế của mọi người. Tại thời điểm này, tôi sẽ gắn cờ câu hỏi là không phù hợp với định dạng của chúng tôi, vì OP đã tuyên bố rõ ràng rằng nó thậm chí không có ý định là một câu hỏi.

Tại thời điểm này tôi không chắc câu trả lời của mình có đúng hay không; có thể không, nhưng nó được hướng vào một số câu hỏi của bạn và có thể nó là một câu trả lời hữu ích cho người khác; người mới bắt đầu lấy lòng, một số người "không" biến thành "làm đôi khi" một khi bạn có nhiều kinh nghiệm hơn. :)

Như một quy tắc chung ...

xin vui lòng tha thứ cho các cạnh thô còn lại; tôi đã dành quá nhiều thời gian cho việc này rồi ... thay vì trích dẫn OP trực tiếp (như dự định ban đầu) tôi sẽ cố gắng tóm tắt và diễn giải.

[phần lớn làm lại từ câu trả lời ban đầu của tôi]
khi xem xét, tôi tin rằng tôi đã đọc sai sự nhấn mạnh mà OP đang đặt vào các câu hỏi tôi đã trả lời; tuy nhiên, các điểm được giải quyết đã được đưa ra và tôi đã để lại các câu trả lời gần như nguyên vẹn vì tôi tin rằng chúng là chính xác và để giải quyết các vấn đề mà tôi đã thấy đưa ra trong các bối cảnh khác cũng như lời khuyên cho người mới bắt đầu.

Bài viết gốc đã hỏi, theo nhiều cách, tại sao các bài viết khác nhau đưa ra lời khuyên như «Đừng phân tích lsđầu ra» hoặc «Bạn không bao giờ nên phân tích lsđầu ra», v.v.

Giải pháp được đề xuất của tôi cho vấn đề này là các trường hợp của loại tuyên bố này chỉ là ví dụ của một thành ngữ, được diễn đạt theo những cách hơi khác nhau, trong đó một bộ lượng hóa tuyệt đối được ghép với một mệnh lệnh [ví dụ: «không [bao giờ] X», «[Bạn nên] luôn luôn Y», «[nên] không bao giờ Z»] để tạo thành các tuyên bố dự định được sử dụng làm quy tắc hoặc hướng dẫn chung, đặc biệt là khi được trao cho những người mới đối với một chủ đề, thay vì được coi là sự thật tuyệt đối, hình thức rõ ràng của những tuyên bố mặc dù.

Khi bạn bắt đầu học môn học mới, và trừ khi bạn hiểu rõ về lý do tại sao bạn cần phải làm khác, thì tốt nhất là bạn nên tuân theo các quy tắc chung được chấp nhận mà không có ngoại lệ, trừ khi có hướng dẫn từ người có kinh nghiệm hơn mà chính mình. Với kỹ năng và kinh nghiệm gia tăng, bạn trở nên có thể xác định hơn nữa khi nào và nếu một quy tắc được áp dụng trong bất kỳ tình huống cụ thể nào. Khi bạn đạt đến một mức độ kinh nghiệm đáng kể, bạn có thể sẽ hiểu lý do đằng sau quy tắc chung ở nơi đầu tiên, và tại thời điểm đó, bạn có thể bắt đầu sử dụng phán đoán của mình xem liệu và lý do nào đằng sau quy tắc này được áp dụng trong tình huống đó, và cũng như liệu có lẽ có những mối quan tâm lấn át.

Và đó là khi một chuyên gia, có lẽ, có thể chọn làm những việc vi phạm "Quy tắc". Nhưng điều đó sẽ không làm cho họ bớt "Quy tắc".

Và, vì vậy, với chủ đề hiện tại: theo quan điểm của tôi, chỉ vì một chuyên gia có thể vi phạm quy tắc này mà không bị đánh sập hoàn toàn, tôi không thấy bất kỳ cách nào bạn có thể biện minh cho người mới bắt đầu rằng "đôi khi" Không thể phân tích lsđầu ra, bởi vì: không phải vậy . Hoặc, ít nhất, chắc chắn nó không đúng cho người mới bắt đầu làm như vậy.

Bạn luôn đặt những con tốt của bạn ở trung tâm; trong phần mở một, một di chuyển; lâu đài trong cơ hội sớm nhất; hiệp sĩ trước các giám mục; một hiệp sĩ trên vành là nghiệt ngã; và luôn đảm bảo rằng bạn có thể thấy tính toán của mình cho đến cuối! (Rất tiếc, xin lỗi, cảm thấy mệt mỏi, đó là vì trò chơi StackExchange.)

Quy tắc, có nghĩa là bị phá vỡ?

Khi đọc một bài viết về một chủ đề được nhắm mục tiêu hoặc có khả năng được đọc bởi những người mới bắt đầu, thường bạn sẽ thấy những thứ như thế này:

  • "Bạn không nên bao giờ làm X."
  • "Không bao giờ làm Q!"
  • "Đừng làm Z."
  • "Một người nên luôn luôn làm Y!"
  • "C, không có vấn đề gì."

Mặc dù những tuyên bố này chắc chắn dường như nêu rõ các quy tắc tuyệt đối và vượt thời gian, nhưng chúng không phải; thay vào đó, đây là một cách để nêu các quy tắc chung [hay còn gọi là "nguyên tắc", "quy tắc ngón tay cái", "những điều cơ bản", v.v.] ít nhất có thể nói là một cách thích hợp để nêu chúng cho những người mới bắt đầu đọc những bài báo đó. Tuy nhiên, chỉ vì chúng được tuyên bố là tuyệt đối, các quy tắc chắc chắn không ràng buộc các chuyên gia và chuyên gia [những người có khả năng là những người đã tóm tắt các quy tắc đó ngay từ đầu, như một cách để ghi lại và truyền đạt kiến ​​thức thu được khi họ xử lý định kỳ các vấn đề trong nghề cụ thể của họ.]

Những quy tắc đó chắc chắn sẽ không tiết lộ cách một chuyên gia sẽ giải quyết vấn đề phức tạp hoặc nhiều sắc thái, trong đó, nói rằng, các quy tắc đó mâu thuẫn với nhau; hoặc trong đó các mối quan tâm dẫn đến quy tắc ở nơi đầu tiên chỉ đơn giản là không áp dụng. Các chuyên gia không sợ (hoặc không nên sợ!) Đơn giản là phá vỡ các quy tắc mà họ tình cờ biết không có ý nghĩa trong một tình huống cụ thể. Các chuyên gia liên tục xử lý việc cân bằng các rủi ro và mối quan tâm khác nhau trong nghề của họ, và phải thường xuyên sử dụng phán đoán của mình để chọn phá vỡ các quy tắc đó, phải cân bằng các yếu tố khác nhau và không thể chỉ dựa vào một bảng quy tắc để tuân theo. Lấy Gotomột ví dụ: đã có một cuộc tranh luận kéo dài, định kỳ về việc chúng có hại hay không. (Yeah, đừng bao giờ sử dụng gotos .; D)

Một đề xuất phương thức

Một tính năng kỳ lạ, ít nhất là bằng tiếng Anh, và tôi tưởng tượng trong nhiều ngôn ngữ khác, theo các quy tắc chung, là chúng được nêu dưới dạng giống như một đề xuất phương thức, nhưng các chuyên gia trong một lĩnh vực sẵn sàng đưa ra một quy tắc chung cho một tình hình, trong khi biết rằng họ sẽ phá vỡ quy tắc khi thích hợp. Rõ ràng, do đó, các câu lệnh này không có nghĩa là tương đương với các câu lệnh tương tự trong logic phương thức.

Đây là lý do tại sao tôi nói họ chỉ đơn giản là thành ngữ. Thay vì thực sự là một tình huống "không bao giờ" hoặc "luôn luôn", các quy tắc này thường phục vụ để mã hóa các hướng dẫn chung có xu hướng phù hợp trong một loạt các tình huống và khi người mới bắt đầu làm theo chúng một cách mù quáng, có khả năng dẫn đến kết quả xa kết quả tốt hơn so với người mới bắt đầu chọn chống lại họ mà không có lý do chính đáng. Đôi khi, họ mã hóa các quy tắc đơn giản dẫn đến kết quả không đạt tiêu chuẩn thay vì các lỗi hoàn toàn đi kèm với các lựa chọn không chính xác khi đi ngược lại các quy tắc.

Vì vậy, các quy tắc chung không phải là các đề xuất phương thức tuyệt đối mà chúng xuất hiện trên bề mặt, mà thay vào đó là một cách viết tắt của quy tắc với một mẫu soạn sẵn tiêu chuẩn ngụ ý, đại loại như sau:

trừ khi bạn có khả năng nói rằng hướng dẫn này không chính xác trong một trường hợp cụ thể và chứng minh với chính mình rằng bạn đúng, thì $ {RULE}

trong đó, tất nhiên bạn có thể thay thế "không bao giờ phân tích lsđầu ra" thay cho $ {RULE}. :)

Ồ vâng! Điều gì về phân tích lsđầu ra?

Chà, vì vậy, với tất cả những điều đó ... tôi nghĩ khá rõ ràng rằng quy tắc này là một quy tắc tốt. Trước hết, quy tắc thực sự phải được hiểu là thành ngữ, như đã giải thích ở trên ...

Nhưng hơn nữa, không chỉ là bạn phải rất giỏi với kịch bản shell để biết liệu nó có thể bị phá vỡ hay không, trong một số trường hợp cụ thể. Ngoài ra, điều đó cũng cần nhiều kỹ năng để nói rằng bạn đã hiểu sai khi bạn đang cố gắng phá vỡ nó trong thử nghiệm! Và, tôi tự tin nói rằng phần lớn khán giả có khả năng của những bài báo như vậy (đưa ra lời khuyên như «Đừng phân tích đầu ra của ls!») Không thể làm những điều đó , và những người có kỹ năng như vậy sẽ có thể nhận ra rằng họ tự mình tìm ra và bỏ qua quy tắc này.

Nhưng ... chỉ cần nhìn vào câu hỏi này, và làm thế nào mà ngay cả những người có lẽ có kỹ năng cũng nghĩ rằng đó là một lời kêu gọi tồi tệ để làm như vậy; và bao nhiêu nỗ lực của tác giả của câu hỏi chỉ dành cho một điểm của ví dụ tốt nhất hiện tại! Tôi đảm bảo với bạn về một vấn đề khó khăn, 99% những người ngoài kia sẽ hiểu sai, và với kết quả rất tệ! Ngay cả khi phương pháp được quyết định hóa ra là một phương pháp tốt; cho đến khi lstoàn bộ ý tưởng phân tích (hoặc khác) được dân số nhà phát triển CNTT / nhà phát triển chấp nhận, chịu được rất nhiều thử nghiệm (đặc biệt là thử nghiệm thời gian) và cuối cùng, quản lý để chuyển sang trạng thái 'kỹ thuật chung', có khả năng là nhiều người có thể thử nó và hiểu sai ... với những hậu quả tai hại.

Vì vậy, tôi sẽ nhắc lại lần cuối .... rằng, đặc biệt trong trường hợp này , đó là lý do tại sao " không bao giờ phân tích lsđầu ra!" được quyết định là cách đúng để diễn đạt nó.

[CẬP NHẬT 2014-05-18: làm rõ lý do trả lời (ở trên) để trả lời nhận xét từ OP; bổ sung sau đây là để đáp ứng với các bổ sung của OP cho câu hỏi từ ngày hôm qua]

[CẬP NHẬT 2014-11-10: thêm tiêu đề và nội dung được sắp xếp lại / tái cấu trúc; và cũng: định dạng lại, viết lại, làm rõ và ... "súc tích" ... tôi dự định đây chỉ đơn giản là một sự dọn dẹp, mặc dù nó đã biến thành một chút của việc làm lại. tôi đã để nó trong trạng thái xin lỗi, vì vậy tôi chủ yếu cố gắng đưa ra một số thứ tự. tôi đã cảm thấy điều quan trọng là phần lớn giữ nguyên phần đầu tiên; vì vậy chỉ có hai thay đổi nhỏ ở đó, dự phòng 'nhưng' đã bị xóa và 'điều đó' nhấn mạnh.]

Ban đầu tôi dự định điều này chỉ là một sự làm rõ trên bản gốc của tôi; nhưng quyết định bổ sung khác khi phản ánh

Xem https://unix.stackexchange.com/tour để biết hướng dẫn về bài đăng


2
Không bao giờ là thành ngữ. Đây không phải là một câu trả lời cho bất cứ điều gì.
mikeerv

1
Hừm. Chà, tôi không biết câu trả lời này có thỏa mãn hay không nhưng tôi hoàn toàn không hy vọng nó sẽ gây tranh cãi . Và, tôi đã không (có nghĩa là) lập luận rằng 'never' là mỗi gia nhập thành ngữ; nhưng đó là "Không bao giờ làm X!" là một cách sử dụng thành ngữ . Tôi thấy hai trường hợp chung có thể cho thấy rằng 'Không bao giờ / không phân tích cú pháp ls!' là lời khuyên chính xác: 1. chứng minh (với sự hài lòng của bạn) rằng mọi trường hợp sử dụng mà người ta có thể phân tích lsđầu ra có một giải pháp khả dụng khác, vượt trội theo một cách nào đó, mà không làm như vậy. 2. cho thấy rằng, trong các trường hợp được trích dẫn, tuyên bố không phải là một nghĩa đen.
Shelleybutoston

Nhìn lại câu hỏi của bạn, tôi thấy rằng trước tiên bạn đề cập đến "không ..." thay vì "không bao giờ ...", điều này cũng phù hợp với phân tích của bạn, vì vậy tôi cũng sẽ làm rõ về điểm đó. Tại thời điểm này, đã có một giải pháp loại đầu tiên, rõ ràng được thể hiện / giải thích cho sự hài lòng của bạn, vì vậy tôi sẽ không đi sâu vào đó nhiều. Nhưng tôi sẽ cố gắng làm rõ câu trả lời của mình một chút: như tôi nói, tôi không cố gây tranh cãi (hay đối đầu!) Mà chỉ ra cách nói chung những dự định đó.
Shelleybutoston

1
Tôi nên dọn sạch bài đăng đó. Tuy nhiên, không bao giờkhông đúng cách để cụm từ nó. Thật là nực cười khi mọi người nghĩ rằng họ đủ điều kiện để nói với người khác không bao giờ hoặc không - chỉ nói với họ rằng bạn không nghĩ nó sẽ hoạt động và tại sao, nhưng bạn biết điều gì sẽ làm việc và tại sao. lslà một tiện ích máy tính - bạn có thể phân tích đầu ra máy tính.
mikeerv

1
Vâng, tôi đã đảo ngược downvote của tôi bởi vì, ít nhất, bạn nói đúng về điều gắn cờ. Ill cố gắng để làm sạch nó tối nay hoặc ngày mai. Tôi nghĩ rằng tôi sẽ chuyển hầu hết các ví dụ mã sang một câu trả lời tôi đoán. Nhưng nó vẫn không, theo như tôi quan tâm, xin lỗi về sự không chính xác trong bài đăng trên blog được trích dẫn. Tôi ước mọi người sẽ ngừng trích dẫn hướng dẫn sử dụng bash hoàn toàn - ít nhất là không cho đến khi họ trích dẫn thông số POSIX ...
mikeerv

16

Có thể phân tích đầu ra lstrong một số trường hợp nhất định? Chắc chắn rồi. Ý tưởng trích xuất danh sách các số inode từ một thư mục là một ví dụ điển hình - nếu bạn biết rằng việc triển khai của bạn lshỗ trợ -q, và do đó mỗi tệp sẽ tạo ra chính xác một dòng đầu ra và tất cả những gì bạn cần là các số inode, phân tích chúng ra khỏi ls -Rai1qđầu ra chắc chắn là một giải pháp có thể. Tất nhiên, nếu tác giả chưa từng thấy lời khuyên như "Không bao giờ phân tích đầu ra của ls" trước đây, thì có lẽ anh ta sẽ không nghĩ về tên tập tin với dòng mới trong đó, và có lẽ sẽ bỏ qua 'q' và kết quả là mã sẽ bị phá vỡ một cách tinh tế trong trường hợp cạnh đó - vì vậy, ngay cả trong trường hợp lsđầu ra của phân tích cú pháp là hợp lý, lời khuyên này vẫn hữu ích.

Vấn đề lớn hơn là, khi một newbie để shell scripting cố gắng để có một con số kịch bản ra (ví dụ) là những gì các tập tin lớn nhất trong một thư mục, hoặc những gì các tập tin gần đây nhất là sửa đổi trong một thư mục, bản năng đầu tiên của ông là để phân tích ls's đầu ra - có thể hiểu được, bởi vì đây lslà một trong những lệnh đầu tiên mà người mới học.

Thật không may, bản năng đó là sai, và cách tiếp cận đó bị phá vỡ. Đáng tiếc hơn nữa, nó bị hỏng một cách tinh vi - nó sẽ hoạt động hầu hết thời gian, nhưng thất bại trong các trường hợp cạnh có thể được khai thác bởi một người có kiến ​​thức về mã.

Người mới có thể nghĩ về ls -s | sort -n | tail -n 1 | awk '{print $2}'một cách để có được tập tin lớn nhất trong một thư mục. Và nó hoạt động, cho đến khi bạn có một tập tin với một khoảng trắng trong tên.

OK, vậy còn ls -s | sort -n | tail -n 1 | sed 's/[^ ]* *[0-9]* *//'? Hoạt động tốt cho đến khi bạn có một tập tin với một dòng mới trong tên.

Việc thêm -qvào lscác đối số có giúp ích khi có một dòng mới trong tên tệp không? Nó có thể trông giống như vậy, cho đến khi bạn có 2 tệp khác nhau chứa một ký tự không in được ở cùng một vị trí trong tên tệp, và sau đó lsđầu ra không cho phép bạn phân biệt tệp nào là lớn nhất. Tồi tệ hơn, để mở rộng "?", Có lẽ anh ta dùng đến vỏ của mình eval- điều này sẽ gây ra vấn đề nếu anh ta nhấn vào một tệp có tên, chẳng hạn,

foo`/tmp/malicious_script`bar

--quoting-style=shellgiúp đỡ (nếu bạn lsthậm chí hỗ trợ nó)? Không, vẫn hiển thị? đối với các ký tự không thể in được, do đó, vẫn còn mơ hồ trong số nhiều trận đấu là lớn nhất. --quoting-style=literal? Không, giống nhau. --quoting-style=localehoặc --quoting-style=ccó thể giúp đỡ nếu bạn chỉ cần in tên của tệp lớn nhất một cách rõ ràng, nhưng có lẽ không phải nếu bạn cần làm gì đó với tệp sau đó - đó sẽ là một bó mã để hoàn tác trích dẫn và quay lại tên tệp thực mà bạn có thể chuyển nó tới, giả sử, gzip.

Và khi kết thúc tất cả công việc đó, ngay cả khi những gì anh ta có là an toàn và chính xác cho tất cả các tên tệp có thể, thì nó không thể đọc được và không thể nhầm lẫn, và có thể được thực hiện dễ dàng hơn, an toàn và dễ đọc bằng trăn hoặc perl hoặc ruby.

Hoặc thậm chí sử dụng các công cụ shell khác - ngoài đỉnh đầu của tôi, tôi nghĩ rằng điều này phải thực hiện thủ thuật:

find . -type f -printf "%s %f\0" | sort -nz | awk 'BEGIN{RS="\0"} END{sub(/[0-9]* /, "", $0); print}'

Và nên có ít nhất là di động như --quoting-stylelà.


Ồ đúng về kích thước - tôi có thể làm điều đó nếu tôi đã thử - tôi có nên không? Im kinda mệt mỏi hoặc toàn bộ điều này - tôi thích câu trả lời của bạn bởi vì bạn không nói không thể hoặc không hoặc bao giờ nhưng thực sự cung cấp ví dụ về lẽ tại sao không và có thể so sánh thế nào khác - cảm ơn bạn.
mikeerv

Tôi nghĩ rằng nếu bạn đã cố gắng, bạn sẽ khám phá ra nó khó hơn nhiều so với bạn nghĩ. Vì vậy, vâng, tôi khuyên bạn nên thử. Tôi sẽ rất vui khi tiếp tục đưa ra những tên tập tin sẽ phá vỡ cho bạn miễn là tôi có thể nghĩ về chúng. :)
trời ơi

Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
terdon

@mikeerv và godlygeek, tôi đã chuyển chủ đề bình luận này để trò chuyện . Xin đừng có những cuộc thảo luận dài như thế này trong các bình luận, đó là những gì trò chuyện dành cho.
terdon
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.