Чому * not * розбір `ls` (і що робити замість цього)?


204

Я послідовно бачу відповіді, цитуючи це посилання, вказуючи остаточно: "Не розбирайте ls!" Це мене турбує з кількох причин:

  1. Здається, інформація в цьому посиланні була прийнята оптом з невеликим питанням, хоча я можу виділити принаймні кілька помилок у випадковому читанні.

  2. Також здається, що проблеми, зазначені в цьому посиланні, не викликали бажання знайти рішення.

З першого пункту:

... коли ви запитуєте [ls]список файлів, виникає величезна проблема: Unix дозволяє практично будь-який символ у назві файлу, включаючи пробіл, нові рядки, коми, символи труби та багато іншого, що ви коли-небудь намагалися використовувати як роздільник, крім NUL. ... lsвідокремлює назви файлів новими рядками. Це добре, поки у вас не буде файлу з новим рядком у його імені. Оскільки я не знаю жодної реалізації, lsяка дозволяє вам скасувати назви файлів із символами NUL замість нових рядків, це не дозволяє нам безпечно отримати список імен ls.

Бампер, правда? Як коли-небудь ми можемо обробляти перелічений набір даних, що закінчується новим рядком для даних, які можуть містити нові рядки? Ну, якщо люди, відповідаючи на запитання на цьому веб-сайті, не робили подібних дій щодня, я можу подумати, що ми потрапили у певну проблему.

Правда, однак, більшість lsреалізацій насправді надають дуже простий api для розбору їх результатів, і ми все це робимо весь час, навіть не усвідомлюючи цього. Ви не тільки можете закінчити ім'я файлу з null, ви можете почати його також з null або з будь-якої іншої довільної рядки, яка може бути бажана. Більше того, ви можете призначити ці довільні рядки для кожного типу файлів . Зверніть увагу:

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^@^@^@$
^@

Про це див. Докладніше.

Тепер це вже наступна частина цієї статті, яка мене дійсно отримує:

$ 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

Проблема в тому, що з виходу ls ви ні комп’ютер не можете сказати, які його частини складають назву файлу. Це кожне слово? Ні. Це кожен рядок? Ні. На це питання немає правильної відповіді, крім: ви не можете сказати.

Також зауважте, як lsіноді вбрано ваші дані файлу (у нашому випадку він перетворив \nсимвол між словами "a" та "newline" у знак питання ...

...

Якщо ви просто хочете повторити всі файли в поточному каталозі, використовуйте forцикл і глобус:

for f in *; do
    [[ -e $f ]] || continue
    ...
done

Автор називає це шаленими іменами, коли lsповертає список імен, що містять кульки оболонки, а потім рекомендує використовувати глобул оболонки для отримання списку файлів!

Розглянемо наступне:

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 визначає-1 і -q lsоперанди так:

-q- Примушуйте кожен екземпляр символів та <tab>s, що не друкуються, записуватися як '?'символ запитання ( ). Реалізації можуть надавати цю опцію за замовчуванням, якщо вихід на термінальний пристрій.

-1- (Цифрова цифра.) Примушуйте вивести один запис на рядок.

Глоббінг не без власних проблем - ?відповідає будь-якому символу, тож декілька ?результатів відповідності у списку будуть збігатися з одним і тим же файлом кілька разів. З цим легко впоратися.

Хоча як це зробити, справа не в цьому - це все-таки не займе багато, адже це показано нижче - мене цікавило, чому ні . Як я вважаю, найкраща відповідь на це питання була прийнята. Я б запропонував вам спробувати зосередитися на тому, щоб розповісти людям, що вони можуть зробити, ніж на тому, що вони не можуть. Ви набагато рідше, як я вважаю, принаймні виявитесь неправильними.

Але навіщо навіть намагатися? Справді, моя основна мотивація полягала в тому, що інші продовжували мені говорити, що я не можу. Я дуже добре знаю, що lsвихід такий самий регулярний і передбачуваний, як ви цього хотіли б, доки ви знаєте, що шукати. Дезінформація мене більше турбує, ніж більшість справ.

Правда, однак, за винятком винятків як Патріка, так і Вуппуса. Відповіді Вемблі (незважаючи на дивовижну реакцію останнього) , я вважаю більшість інформації у відповідях тут, як правило, правильними - глобус оболонки як простіший у використанні і, як правило, ефективніше шукати поточний каталог, ніж розбирати ls. Вони, однак, принаймні , в моєму ставленні, достатньо підстав , щоб виправдати або поширюючи дезінформацію цитований в статті вище , ні вони поважна не " ніколи не розібрати ls. »

Зверніть увагу, що непослідовні результати відповіді Патріка - це в основному результат його використання zshтоді bash. zsh- за замовчуванням - не розділяє слова, $(замінені )результатами на портативний спосіб. Тож коли він запитує, куди пішли інші файли? відповідь на це питання - ваша шкаралупа їх з'їла. Ось чому вам потрібно встановити SH_WORD_SPLITзмінну при використанні zshта роботі з портативним кодом оболонки. Я вважаю його неспроможність зазначити це у своїй відповіді як жахливо оманливе.

Відповідь Wumpus не розраховує для мене - в контексті списку ?персонаж є глобусом оболонки. Я не знаю, як ще сказати це.

Для вирішення кількох випадків результатів потрібно обмежити жадібність глобуса. Далі просто створимо тестову базу жахливих імен файлів і відобразимо їх для вас:

{ 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" $# )
}

ВИХІД

`ls` ?QUOTED `-m` COMMA,SEP
??\, ??^, ??`, ??b, [?\, [?\, ]?^, ]?^, _?`, _?`, a?b, a?b

NOW LITERAL - COMMA,SEP
?
 \, ?
     ^, ?
         `, ?
             b, [       \, [
\, ]    ^, ]
^, _    `, _
`, a    b, a
b

FILE COUNT: 12

Тепер я буду в безпеці кожен символ , який не є /slash, -dash, :colonабо буквено-цифрового символу в Glob оболонки потім sort -uсписок для унікальних результатів. Це безпечно, оскільки lsвже видалило для нас будь-які символи, що не друкуються. Дивитися:

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

ВИХІД:

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'

Нижче я знову підходжу до проблеми, але використовую іншу методологію. Пам'ятайте, що - крім \0нуля - /символ ASCII - єдиний байт, заборонений у імені шляху. Я відкладаю глобуси тут і замість цього поєдную вказаний -dдля POSIX параметр lsі також задану POSIX -exec $cmd {} +конструкцію find. Оскільки findв природному режимі випускається лише один /послідовно, наступне легко створює рекурсивний і надійно розмежений список файлів, включаючи всю інформацію про стоматологію для кожного запису. Уявіть собі, що ви можете зробити з чимось подібним:

#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 може бути дуже корисним - особливо, коли питання про унікальність результату.

ls -1iq | 
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' | 
tr -d '\n' | 
xargs find

Це лише найбільш портативний засіб, про який я можу придумати. З GNU lsви можете:

ls --quoting-style=WORD

І останнє, ось набагато простіший метод розбору,ls який я буваю використовувати досить часто, коли потребують чисел inode:

ls -1iq | grep -o '^ *[0-9]*'

Це просто повертає числа inode - що є ще однією зручною опцією POSIX.


12
@mikeserv Добре, що я. Шелл-глобус у 2,48 рази швидший. time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'= 3,18s vs time bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'= 1,28s
Патрік

28
Щодо останнього оновлення, будь ласка, перестаньте покладатися на візуальний вихід, як визначити, що ваш код працює. Передайте висновок фактичній програмі і спробуйте програму виконати операцію над файлом. Ось чому я використовував statу своїй відповіді, оскільки він фактично перевіряє наявність кожного файлу. Ваш шматочок внизу з sedріччю не працює.
Патрік

57
Ви не можете бути серйозними. Як перестрибувати всі обручі, описані вашим запитанням, бути простішими або простішими або будь-якими способами краще, ніж просто не розбирати lsв першу чергу? Те, що ви описуєте, дуже важко. Мені потрібно деконструювати це, щоб зрозуміти все це, і я відносно компетентний користувач. Ви не можете очікувати, що ваш середній Джо зможе впоратися з чимось подібним.
тердон

46
-1 для використання питання для вибору аргументу. Усі причини lsнеправильного розбору результатів були добре висвітлені у вихідному посиланні (і в багатьох інших місцях). Це питання було б розумним, якби ОП просили допомогти зрозуміти це, але натомість ОП просто намагається довести його неправильне використання - це нормально.
Р ..

14
@mikeserv Це не просто так parsing ls is bad. Робити for something in $(command)і покладатися на розбиття слів, щоб отримати точні результати, погано для більшості людей, command'sякі не мають простого результату.
BroSlow

Відповіді:


184

Я зовсім не переконаний у цьому, але припустимо, заради аргументу, що ви могли б , якщо ви готові докладати достатньо зусиль, lsнадійно проаналізувати результат навіть перед обличчям "противника" - того, хто знає код, який ви написали, і свідомо вибирає імена файлів, призначені для його порушення.

Навіть якби ви могли це зробити, це все одно буде поганою ідеєю .

Оболонка Борна не є доброю мовою. Його не слід використовувати для нічого складного, якщо тільки крайня портативність не є важливішою, ніж будь-який інший фактор (наприклад autoconf).

Я стверджую, що якщо ви зіткнулися з проблемою, коли розбір результатів lsздається, що шлях найменшого опору для сценарію оболонки, це є чітким свідченням того, що все, що ви робите, є занадто складним для оболонки, і вам слід переписати всю справу в Perl або Python. Ось ваша остання програма в 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))

Тут не виникає жодних проблем з незвичними символами у назви файлів - вихід неоднозначний так само, як вихід lsнеоднозначний, але це не має значення в "реальній" програмі (на відміну від такої демо-версії), яка б використовувати результат os.path.join(subdir, f)безпосередньо.

Не менш важливо, і на відміну від того, що ви написали, воно буде мати сенс через півроку, і це буде легко модифікувати, коли вам потрібно зробити щось трохи інше. Для ілюстрації, припустимо, ви виявите необхідність виключити dotfiles та резервні копії редактора та обробити все в алфавітному порядку за базовою назвою:

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
Це добре. Це for in | for inговорить про рекурсію? Я не впевнений. Навіть якщо це не може бути більше одного, правда? Це єдина відповідь, яка має для мене сенс поки що.
mikeserv

10
Без рекурсії, просто вкладені- forпетлі. os.walkзаймається серйозним важким підйомом за лаштунками, але вам не доведеться турбуватися про це більше, ніж вам потрібно турбуватися про те, як працювати lsабо findвсередині країни.
zwol

6
Технічно os.walkповертає генераторний об'єкт . Генератори - версія лінивих списків Python. Кожен раз, коли зовнішній цикл ітераціюється, генератор викликає і "видає" вміст іншого підкаталогу. Еквівалентна функціональність в Perl є File::Find, якщо це допомагає.
zwol

6
Ви повинні знати, що я на 100% згоден з документом, який ви критикуєте, та з відповідями Патріка та Тердона. Моя відповідь мала на меті дати додаткову , незалежну причину, щоб уникнути розбору lsрезультатів.
zwol

19
Це дуже вводить в оману. Shell не є хорошою мовою програмування, але тільки тому, що це не мова програмування. Це сценарна мова. І це гарна сценарна мова.
Майлз Рут

178

На це посилання багато посилається, оскільки інформація є абсолютно точною, і вона там є дуже давно.


lsтак, замінюються символи, що не надрукуються, символами глобута так, але ці символи відсутні у фактичному імені файлу. Чому це має значення? 2 причини:

  1. Якщо ви передаєте це ім'я файлу програмі, це ім'я фактично не існує. Щоб отримати справжнє ім'я файлу, доведеться розширити глобус.
  2. Глобальний файл може відповідати більш ніж одному файлу.

Наприклад:

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

Зверніть увагу, як у нас є два файли, які виглядають абсолютно однаково. Як ви їх розмежуєте, якщо вони обоє представлені як a?b?


Автор називає це набором імен файлів, коли ls повертає список файлів, що містять кульки оболонки, а потім рекомендує використовувати глобул оболонки для отримання списку файлів!

Тут є різниця. Коли ви повернете глобус, як показано, цей глобус може відповідати більш ніж одному файлу. Однак, коли ви повторюєте результати, що відповідають глобальному, ви отримуєте точний файл, а не глобус.

Наприклад:

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

Зверніть увагу, як xxdпоказує результат, який $fileмістив нераціональні символи, \tа \nне ?.

Якщо ви використовуєте ls, ви отримуєте це замість цього:

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

"Я все одно збираюся повторити, чому б не використовувати ls?"

Ваш приклад, який ви навели, насправді не працює. Схоже, це працює, але це не так.

Я маю на увазі це:

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

Я створив каталог з купою імен файлів:

$ 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

Коли я запускаю ваш код, я отримую таке:

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

Куди пішли інші файли?

Спробуємо це замість цього:

$ 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

Тепер давайте використовувати фактичний глобус:

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

З баш

Наведений вище приклад був із моєю звичайною оболонкою, zsh. Коли я повторюю процедуру з bash, я отримую ще один абсолютно інший набір результатів з вашим прикладом:

Той самий набір файлів:

$ 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

Докорінно різні результати з вашим кодом:

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

З глобусом оболонки він прекрасно працює:

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

Причина, по якій Bash поводиться таким чином, повертається до одного із пунктів, які я зробив на початку відповіді: "Файловий файл може відповідати більше ніж одному файлу".

lsповертає один і той же glob ( a?b) для декількох файлів, тому кожен раз, коли ми розширюємо цей глобул, ми отримуємо кожен файл, який відповідає йому.


Як відтворити список файлів, які я використовував:

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

Шістнадцятковий код - це символи NBSP UTF-8.


5
@mikeserv насправді його рішення не повертає глобус. Я просто оновив свою відповідь, щоб уточнити цю точку.
Патрік

18
"Не решта"? Це непослідовна поведінка та несподівані результати, як це не причина?
Патрік

11
@mikeserv Ви не бачили мого коментаря до вашого питання? Глобулярна оболонка в 2,5 рази швидша, ніж ls. Я також просив вас протестувати свій код, оскільки він не працює. Що стосується zsh з будь-яким із цього?
Патрік

27
@mikeserv Ні, це все ще стосується навіть bash. Хоча я і закінчуюсь цим питанням, оскільки ти не слухаєш те, що я говорю.
Патрік

7
Ви знаєте що, я думаю, що я підтримаю цю відповідь і уточню, що я згоден з усім, що вона говорить. ;-)
zwol

54

Спробуємо і трохи спростимо:

$ 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

Побачити? Це вже неправильно там. Існує 3 файли, але bash повідомляє 4. Це відбувається тому set, що надаються глобуси, згенеровані lsякими розширюються оболонкою перед передачею set. Ось чому ви отримуєте:

$ 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

Або, якщо ви віддаєте перевагу:

$ 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

Наведене було запущено далі bash 4.2.45.


2
Я це підтримав. Добре бачити, як ваш власний код кусає вас. Але те, що я помилився, не означає, що це не може бути зроблено правильно. Я показав вам дуже простий спосіб зробити це сьогодні вранці ls -1qRi | grep -o '^ *[0-9]*'- це розбір lsрезультатів, людино, і це найшвидший і найкращий спосіб, з якого я знаю, щоб отримати список номерів inode.
mikeserv

38
@mikeserv: Це можна зробити правильно, якщо у вас є час і терпіння. Але справа в тому, що вона по своїй суті схильна до помилок. Ви самі це неправильно зрозуміли. при цьому сперечаючись про його достоїнства! Це величезний страйк проти нього, якщо навіть той, хто бореться за це, не зможе зробити це правильно. І швидше за все, ви, ймовірно, витратите ще більше часу на те, щоб помилитися, перш ніж правильно зрозуміти. Я не знаю про вас, але більшості людей краще займатися своїм часом, ніж поспіль на віки з тим самим рядком коду.
cHao

@cHao - я не аргументував її достоїнств - я протестував проти його пропаганди.
mikeserv

16
@mikeserv: Аргументи проти цього є обґрунтованими та заслуженими. Навіть ви показали, що вони правдиві.
cHao

1
@cHao - я не згоден. Між мантрою та мудрістю існує не дуже тонка грань.
mikeserv

50

Вихід ls -qне зовсім глобус. Це ?означає, що "Тут є символ, який не може бути відображений безпосередньо". Глобуси використовують ?для позначення "Тут дозволено будь-який символ".

У глобусів є інші спеціальні символи ( *і []принаймні, а всередині []пари більше). Жоден із них не уникнути ls -q.

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

Якщо ви обробляєте ls -1qвихід, є набір глобусів і розширюєте їх, не тільки ви отримаєте xдвічі, але і [x]зовсім пропустите . Як глобус, він не відповідає собі як рядок.

ls -q покликаний врятувати очі та / або термінал від божевільних персонажів, а не створювати щось, що можна повернути до оболонки.


42

Відповідь проста: Особливі випадки, які lsвам доведеться вирішити, переважають будь-яку можливу вигоду. Цих особливих випадків можна уникнути, якщо ви не аналізуєте lsвихід.

Мантра тут ніколи не довіряє користувальницькій файловій системі (еквівалент ніколи не довіряє вводу користувача ). Якщо є метод, який буде працювати завжди, зі 100% впевненістю, це повинен бути той метод, який ви віддаєте перевагу, навіть якщо lsробить те саме, але з меншою визначеністю. Я не буду вникати в технічні деталі, оскільки ці питання охоплені тердоном та Патріком . Я знаю, що через ризики використання lsу важливій (і, можливо, дорогої) транзакції, де моя робота / престиж є на межі, я віддаю перевагу будь-якому рішенню, яке не має ступеня невизначеності, якщо цього можна уникнути.

Я знаю, що деякі люди віддають перевагу певним ризикам , але я подав звіт про помилки .


33

Причина, по якій люди кажуть, що ніколи щось не робить, не обов'язково, тому що це абсолютно позитивно неможливо зробити правильно. Ми можемо це зробити, але це може бути складніше, менш ефективно як в просторі, так і в часі. Наприклад, було б чудово сказати: "Ніколи не будуйте великий сервер електронної комерції у складі x86".

Тож тепер до проблеми, що підходить до вас: Як ви продемонстрували, ви можете створити рішення, яке аналізує ls і дає правильний результат - тому правильність не є проблемою.

Це складніше? Так, але ми можемо приховати це за функцією помічника.

Тож тепер до ефективності:

Ефективність простору: Ваше рішення покладається на uniqфільтрацію дублікатів, отже, ми не можемо генерувати результати. Так що або O(1)проти, O(n)або і те й інше O(n).

Ефективність часу: найкращий випадок uniqвикористовує хешмап-підхід, тому у нас все ще є O(n)алгоритм щодо кількості закуплених елементів , мабуть, так і є O(n log n).

Тепер справжня проблема: Хоча ваш алгоритм все ще не виглядає занадто погано, я дуже обережно використовував закуплені елементи, а не елементи для n. Тому що це має велике значення. Скажімо, у вас є файл, \n\nякий призведе до глобусу, ??щоб відповідати кожному двома символьним файлом у списку. Смішно, якщо у вас є інший файл, \n\rякий також призведе до, ??а також поверне всі два символьні файли .. подивіться, куди це йде? Експоненціальна замість лінійної поведінки, безумовно, кваліфікується як "гірша поведінка під час виконання". Це різниця між практичним алгоритмом і тим, про який ви пишете документи в теоретичних журналах CS.

Всі люблять приклади, правда? Ось і ми. Створіть папку під назвою "test" та використовуйте цей скрипт python у тому самому каталозі, де папка.

#!/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()

Єдине, що потрібно зробити - це створити всі продукти довжиною 3 для 7 символів. Математика середньої школи говорить нам, що повинно бути 343 файли. Ну, це повинно бути дуже швидким для друку, тому подивимось:

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

Тепер спробуємо ваше перше рішення, бо я справді цього не можу отримати

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)

Тут потрібно працювати над Linux mint 16 (що, на мій погляд, говорить про зручність використання цього методу).

Так чи інакше, оскільки вищезгадане в значній мірі лише фільтрує результат після його отримання, попереднє рішення повинно бути як мінімум таким же швидким, як і пізніше (жодних хитрощів у цьому немає, але вони недостовірні, щоб ви відмовилися від правильності).

Тож тепер як довго

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

брати? Ну, я справді не знаю, потрібно тривати час, щоб перевірити назви файлів 343 ^ 343 - я скажу вам після теплової смерті Всесвіту.


6
Звичайно, як згадується в коментарях під іншою відповіддю , твердження "... ви продемонстрували, що можете створити рішення, яке розбирає ls і дає правильний результат ..." насправді не відповідає дійсності.
Wildcard

26

Описаний намір ОП

передмова та обґрунтування оригінальної відповіді оновлено 18.05.2015

mikeserv (OP) в останньому оновленні свого запитання заявив: вважаю соромним, хоча я вперше поставив це питання, щоб вказати на джерело дезінформації, і, на жаль, найбільш відповідна відповідь тут значною мірою є оманливою. "

Ну гаразд; Я відчуваю, що це було прикро, що я витратив стільки часу, намагаючись зрозуміти, як пояснити своє значення, лише щоб знайти це , перечитуючи питання. Це питання закінчилося "[породженням] дискусії, а не відповідей" і закінчилося зважуванням у ~ 18 К тексту (лише для питання, щоб було зрозуміло), що буде довго навіть для публікації в блозі.

Але StackExchange - це не ваша мильна скринька, і не ваш блог. Однак, по суті, ви використали його як принаймні трохи обох. Люди закінчили витрачати багато часу на відповіді на ваш "To-Point-Out", а не на відповіді на фактичні запитання людей. На даний момент я позначаю це питання як невідповідний для нашого формату, враховуючи, що ОП прямо заявила, що це взагалі не було задумом.

На даний момент я не впевнений, відповів я на то, чи ні; напевно, ні, але це було спрямовано на деякі ваші запитання, і, можливо, це може бути корисною відповіддю для когось іншого; Початківці приймають серце, деякі з тих, хто не перетворюється на "інколи", отримують досвід. :)

Як загальне правило ...

будь ласка, пробачте залишилися шорсткі краї; я витратив надто багато часу на це вже ... замість того, щоб цитувати ОП безпосередньо (як спочатку передбачалося), я спробую узагальнити та перефразувати.

[значною мірою перероблений з моєї оригінальної відповіді]
після розгляду, я вважаю, що я неправильно прочитав акценти, які ОП ставила на питання, на які я відповіла; однак порушені моменти були підняті, і я залишив відповіді в цілому недоторканими, оскільки я вважаю, що вони є точними і вирішувати питання, які я бачив, підняті в інших контекстах, а також щодо порад для початківців.

Оригінальний пост декілька способів запитував, чому різні статті давали поради, такі як «Не розбирати lsвихід» або «Ніколи не слід аналізувати lsвихід» тощо.

Я запропонував вирішити питання про те, що екземпляри такого роду твердження - це просто приклади ідіоми, сформульованої дещо по-іншому, коли абсолютний кількісний показник поєднується з імперативом [наприклад, «не [ніколи] X», «[Ви повинні] завжди Y», «[ніхто не повинен] ніколи»] формувати висловлювання, призначені для використання в якості загальних правил або вказівок, особливо коли вони даються новим для теми, а не вважаються абсолютними істинами, Явна форма цих тверджень, незважаючи на це.

Коли ви починаєте вивчати нову тематику, і якщо ви не маєте хорошого розуміння того, чому вам може знадобитися робити інше, корисно просто слідувати прийнятим загальним правилам без винятку - якщо тільки під керівництвом когось більш досвідченого що ти сам. Завдяки зростанню навичок та досвіду ви зможете визначати, коли і чи застосовується правило в будь-якій конкретній ситуації. Як тільки ви досягнете значного рівня досвіду, ви, швидше за все, зрозумієте міркування, що стоять за загальним правилом, і в цей момент ви зможете почати використовувати своє судження про те, і на якому рівні застосовуються причини, що стоять за правилом така ситуація, а також щодо того, чи існують, можливо, переважаючі проблеми.

І саме тоді експерт, можливо, може вирішити робити справи з порушенням "Правил". Але це не зробило б їх меншими "Правилами".

І, отже, до цієї теми: на мою думку, якраз тому, що експерт може порушити це правило, не будучи повністю примхнутим, я не бачу жодного способу, який би ви могли виправдати, сказавши новачеві, що "іноді" це нормально проаналізувати lsвихід, тому що: це не так . Або, принаймні, для початківця, звичайно, це не правильно.

Ви завжди кладете пішаки в центр; у отворі одна деталь, один хід; замок при першій же можливості; лицарі перед єпископами; лицар на обідці похмурий; і завжди переконайтеся, що зможете переглянути свій розрахунок до кінця! (Ну, вибачте, втомившись, це для шахів StackExchange.)

Правила, що мають бути порушені?

Читаючи статтю на тему, на яку орієнтовані або, ймовірно, читатимуть початківці, часто ви побачите такі речі:

  • «Ви не повинні коли - або робити X.»
  • "Ніколи не роби Q!"
  • "Не роби Z."
  • "Завжди треба робити Y!"
  • "C, незважаючи ні на що".

Хоча ці твердження, безумовно, здаються абсолютними та позачасовими правилами, вони не є; натомість це спосіб викладення загальних правил [він же "настанови", "великі правила", "основи" тощо), що є, принаймні, одним із підходящих способів констатувати їх для початківців, які можуть читати ці статті. Однак, лише тому, що вони заявляються як абсолютні, правила, безумовно, не зобов'язують професіоналів та експертів [які, швидше за все, були тими, хто узагальнив такі правила, в першу чергу, як спосіб записувати та передавати знання, отримані в процесі їх повторення. проблеми в їх конкретному ремеслі.]

Ці правила, безумовно, не розкриють, як експерт поводитиметься зі складною або нюансованою проблемою, в якій, скажімо, ці правила суперечать один одному; або коли проблеми, які призвели до цього правила, в першу чергу просто не стосуються. Експерти не бояться (або не повинні їх боятися!) Просто порушувати правила, які, як вони знають, не мають сенсу в конкретній ситуації. Експерти постійно мають справу з врівноваженням різних ризиків та проблем у своїй галузі, і вони часто повинні використовувати своє судження, щоб вирішити, чи не порушувати такі правила, зрівноважуючи різні фактори і не в змозі просто покластися на таблицю правил, яку слід дотримуватися. Візьмемо Gotoдля прикладу: тривали тривалі дискусії щодо шкідливості. (Так, не завжди використовують послідовно відкриває ;. D)

Модальна пропозиція

Незвичайна особливість, принаймні англійською мовою, і я думаю, що в багатьох інших мовах із загальних правил полягає в тому, що вони викладені в тій же формі, що і модальна пропозиція, але експерти в цій галузі готові дати загальне правило для ситуація, весь час знаючи, що вони порушують правило, коли це доречно. Зрозуміло, отже, ці твердження не мають бути еквівалентними тим же твердженням в модальній логіці.

Ось чому я кажу, що вони повинні бути просто ідіоматичними. Замість того, щоб по-справжньому бути ситуацією "ніколи" або "завжди", ці правила, як правило, служать для кодифікації загальних вказівок, які, як правило, підходять для широкого кола ситуацій, і що, коли початківці сліпо слідують за ними, швидше за все, це призведе до далеко кращі результати, ніж початківець, який вирішив піти проти них без поважних причин. Іноді вони кодифікують правила, що просто призводять до нестандартних результатів, а не відвертих збоїв, що супроводжують неправильний вибір, коли йдеться проти правил.

Таким чином, загальні правила не є абсолютними модальними пропозиціями, які вони, як видається, знаходяться на поверхні, а натомість є скороченим способом подання правила із загальною стандартною котловою плиткою, приблизно таким:

якщо ви не маєте змоги сказати, що цей настанова в конкретному випадку неправильний, і довести собі, що ви маєте рацію, то $ {RULE}

де, звичайно, ви можете замінити "never parse lsoutput" замість $ {RULE}. :)

О так! Що Про синтаксичні lsвиході?

Ну, так, враховуючи все це ... я думаю, що цілком зрозуміло, що це правило є хорошим. Перш за все, слід розуміти справжнє правило як ідіоматичне, як пояснено вище ...

Але крім того, не просто ви повинні бути дуже хорошими в сценаріях оболонок, щоб знати, чи може вона бути зламана в якомусь конкретному випадку. Окрім того, потрібна стільки ж майстерності, щоб сказати, що ви помилилися, коли ви намагаєтесь зламати це під час тестування! І я впевнено кажу, що дуже велика більшість ймовірної аудиторії таких статей (даючи поради типу «Не розбирайте результати ls!») Не можуть робити цього , і ті, хто має таку майстерність, швидше за все зрозуміють, що вони розуміють це самостійно і все одно ігнорують правило.

Але ... просто подивіться на це питання, і як навіть люди, які, мабуть, мають майстерність, вважали, що це було погано закликати до цього; і скільки зусиль автор питання витратив лише на те, щоб дійти до точки найкращого прикладу! Я гарантую вам проблему, що тяжко, 99% людей там би помилилися, і з потенційно дуже поганими результатами! Навіть якщо метод, на який зважився, виявиться хорошим; поки її (або інша) lsідея розбору не буде прийнята IT / розробником в цілому, витримує безліч тестувань (особливо тест часу) і, нарешті, не встигає перейти до статусу "загальної техніки", ймовірно, що багато людей можуть спробувати це, і помиляються ... із згубними наслідками.

Отже, я повторю востаннє .... що, особливо в цьому випадку , що саме тому « ніколи не розібрати lsвихід!» це, безумовно, правильний спосіб сформулювати це.

[ОНОВЛЕННЯ 2014-05-18: уточнені міркування для відповіді (вище) для відповіді на коментар ОП; наступне доповнення - відповідь на доповнення ОП до запитання від вчора]

[ОНОВЛЕННЯ 2014-11-10: додані заголовки та реорганізований / відновлений контент; а також: переформатування, переформатування, уточнення та гм ... "стислий-хоч" ... Я задумав це просто очищення, хоча це перетворилося на трохи переробку. я покинув це в сумному стані, тому я в основному намагався надати йому якийсь наказ. я відчував, що важливо значною мірою залишити перший розділ недоторканим; тому лише дві незначні зміни там - зайві ", але" вилучено та ", що підкреслили.]

† Спочатку я задумував це виключно як уточнення свого оригіналу; але вирішив інші доповнення після роздумів

‡ див. Https://unix.stackexchange.com/tour для вказівки щодо публікацій


2
Ніколи не є ідіоматичним. Це не відповідь ні на що.
mikeserv

1
Хм. Ну, я не знав, чи задовольнятиме ця відповідь, але я абсолютно не очікував, що це буде суперечливо . І я (не маю на увазі) стверджувати, що «ніколи» не було само по собі ідіоматичним; але це "Ніколи не роби X!" є ідіоматичним вживанням . Я бачу два загальні випадки, які показують, що "Ніколи / не розбирай ls!" правильна порада: 1. продемонструйте (на ваш задоволення), що кожен випадок використання, коли можна проаналізувати lsвихід, має інше доступне рішення, якимсь чином перевершує, не роблячи цього. 2. покажіть, що у цитованих випадках твердження не є буквальним.
shelleybutterfly

Знову дивлячись на ваше запитання, я бачу, що ви спочатку згадуєте "не ...", а не "ніколи ...", що добре входить до вашого аналізу, тому я уточню і на цьому. На даний момент вже є рішення першого типу, яке, очевидно, продемонстровано / пояснено на ваше задоволення, тому я не буду там дуже сильно заглиблюватися. Але я спробую трохи уточнити свою відповідь: як я кажу, я намагався не бути суперечливим (або конфронтаційним!), А вказати, як ці твердження взагалі призначені.
shelleybutterfly

1
Я повинен прибрати цю посаду. Тим НЕ менше, ніколи не є НЕ правильним способом вираження його. Це трохи смішно , що люди думають , що Theyre кваліфікований, щоб сказати іншим , ніколи або DonT - просто сказати їм , ви не думаєте , він буде працювати і чому, але ви знаєте , що буде працювати і чому. ls- це утиліта для комп'ютера - ви можете проаналізувати вихід комп'ютера.
mikeserv

1
Що ж, я перевернув свою позицію, тому що, принаймні, ти маєш рацію щодо того, що позначаєш. Я спробую прибрати це сьогодні вночі чи завтра. Думаю, що я переміщу більшість прикладів коду до відповіді, яку я здогадуюсь. Але це все ще не дає виправдання неточностей у тому, що часто цитується в блозі. Я б хотів, щоб люди взагалі перестали цитувати посібник з башти - принаймні, не до того, як вони цитують специфікації POSIX ...
mikeserv

16

Чи можливо проаналізувати вихід lsу певних випадках? Звичайно. Ідея вилучення списку номерів inode з каталогу є хорошим прикладом - якщо ви знаєте, що ваша програма lsпідтримує -q, і тому кожен файл видасть рівно один рядок виводу, і все, що вам потрібно, це номери inode, які аналізують їх з ls -Rai1qвихід, безумовно, можливе рішення. Звичайно, якби автор раніше не бачив порад, як "Ніколи не розбирати вихід ls", він, ймовірно, не замислюється про назви файлів з новими рядками в них, і, ймовірно, залишить "q" в результаті, і Код був би тонко розбитий у цьому крайовому випадку - тому, навіть у випадках, коли lsвихід синтаксичного аналізу є розумним, ця порада все ще корисна.

Чим ширше Справа в тому , що, коли новачок в мові сценаріїв командної оболонки намагається мати фігуру сценарію з (наприклад) , що це найбільший файл в каталозі, або те , що це зовсім недавно змінений файл в каталозі, його перший інстинкт для розбору ls«пд.ш. вихід - зрозумілий, тому що lsце одна з перших команд, яку навчить новачок.

На жаль, цей інстинкт неправильний, і такий підхід порушений. На жаль, він тонко зламаний - він працюватиме більшу частину часу, але провалюється у крайніх випадках, які, можливо, можуть бути використані хтось із знанням коду.

Новачок може розглянути ls -s | sort -n | tail -n 1 | awk '{print $2}'як спосіб отримати найбільший файл у каталозі. І працює, поки у вас не буде файлу з пробілом в імені.

Гаразд, а як же ls -s | sort -n | tail -n 1 | sed 's/[^ ]* *[0-9]* *//'? Відмінно працює, поки у вас не буде файлу з новим рядком в імені.

Чи -qдопомагає додавання до ls's аргументів, коли у назві файлу є новий рядок? Це може виглядати так, поки у вас є два різних файли, які містять недрукувальний символ на тому ж місці у назві файлу, і тоді lsвихідний результат не дозволить вам розрізнити, який із них був найбільшим. Гірше, щоб розширити "?", Він, ймовірно, вдається до своєї оболонки eval- що спричинить проблеми, якщо він потрапить на файл, названий, наприклад,

foo`/tmp/malicious_script`bar

Чи --quoting-style=shellдопомагає (якщо ваш lsнавіть це підтримує)? Ні, все ще відображається? для персонажів, що не друкуються, тому все ще неоднозначно, який із кількох матчів був найбільшим. --quoting-style=literal? Ні, ж. --quoting-style=localeабо --quoting-style=cможе допомогти, якщо вам просто потрібно однозначно надрукувати ім’я найбільшого файлу, але, мабуть, ні, якщо після цього вам потрібно щось зробити з файлом - це було б куповим кодом, щоб скасувати цитування та повернутися до справжнього імені файлу, так що ви можете передати його, скажімо, до gzip.

І наприкінці всієї роботи, навіть якщо те, що він має, є безпечним і правильним для всіх можливих імен, це не читабельно і неможливо, і це можна було зробити набагато легше, безпечніше і читабельніше в пітоні, перлі чи рубіні.

Або навіть використовуючи інші інструменти для оболонки - вгорі голови, я думаю, що це повинно зробити трюк:

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

І він повинен бути принаймні таким же портативним --quoting-style.


О правда щодо розміру - я, мабуть, міг би це зробити, якби спробував - чи повинен? Im цікаве втомився або вся ця річ - мені подобається ваша відповідь , тому що ви не говорите не може або не робите або ніколи , але на самому справі навести приклади , може бути , чому немає і порівнянними , як ще - спасибі.
mikeserv

Я думаю, якби ти спробував, ти виявив би це набагато важче, ніж ти думаєш. Отже, так, я б рекомендував спробувати. Я буду радий продовжувати надавати імена файлів, які будуть порушуватись для вас, поки я можу їх думати. :)
godlygeek

Коментарі не для розширеного обговорення; ця розмова перенесена в чат .
terdon

@mikeserv та godlygeek, я перемістив цю тему коментарів до чату . Будь ласка, не майте довгих дискусій, як це в коментарях, саме для цього і спілкується в чаті.
terdon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.