Почему * не * разбирать `ls` (и что вместо этого делать)?


204

Я постоянно вижу ответы, цитирующие эту ссылку с указанием "Не разбирайся ls!" Это беспокоит меня по нескольким причинам:

  1. Кажется, что информация в этой ссылке была принята оптом с небольшим вопросом, хотя я могу выделить по крайней мере несколько ошибок при случайном чтении.

  2. Также кажется, что проблемы, указанные в этой ссылке, не вызвали желания найти решение.

Из первого абзаца:

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

Облом, верно? Как всегда мы можем справиться с новой строки завершается перечисленный набор данных для данных , которые могут содержать символы новой строки? Ну, если бы люди, отвечающие на вопросы на этом сайте, не делали такого рода вещи ежедневно, я мог бы подумать, что у нас были некоторые проблемы.

Правда в том, что большинство lsреализаций на самом деле предоставляют очень простой API для анализа их вывода, и мы все делали это все время, даже не осознавая этого. Мало того, что вы можете завершить имя файла с нуля, вы также можете начать с нуля или с любой другой произвольной строки, которую вы можете пожелать. Более того, вы можете назначить эти произвольные строки для каждого типа файла . Пожалуйста примите к сведению:

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 - единственный байт, запрещенный в имени пути. Здесь я откладываю globs и вместо этого комбинирую указанную -dдля POSIX опцию для lsи указанную -exec $cmd {} +для POSIX конструкцию для 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 который я использую довольно часто, когда нужны номера инодов:

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

Это просто возвращает номера инодов - это еще одна удобная опция, указанная в POSIX.


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

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

57
Ты не можешь быть серьезным. Как прыгать через все обручи, которые описывает ваш вопрос, проще или проще, или лучше, чем просто не разбирать ls? То, что вы описываете, очень сложно. Мне нужно разобрать его, чтобы понять все это, и я относительно компетентный пользователь. Вы не можете ожидать, что ваш средний Джо сможет справиться с чем-то вроде этого.
Тердон

46
-1 за использование вопроса для выбора аргумента. Все причины, по которым lsвывод синтаксического анализа является неправильным, были хорошо освещены в исходной ссылке (и во многих других местах). Этот вопрос был бы разумным, если бы ОП просил помочь понять его, но вместо этого ОП просто пытается доказать, что его неправильное использование в порядке.
R ..

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)напрямую.

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

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. Каждый раз, когда внешний цикл for повторяется, генератор вызывается и «возвращает» содержимое другого подкаталога. Эквивалентная функциональность в 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

Обратите внимание, что у нас есть 2 файла, которые выглядят одинаково. Как вы собираетесь отличить их, если они оба представлены как 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

Шестнадцатеричный код - это символы UTF-8 NBSP.


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вывода, чувак, и это самый быстрый и лучший из известных мне способов получения списка номеров инодов.
mikeserv

38
@mikeserv: Это можно сделать правильно, если у вас есть время и терпение. Но дело в том, что он по своей природе подвержен ошибкам. Вы сами ошиблись. пока спорю о его достоинствах! Это огромный удар по нему, если даже один человек, сражающийся за это, не сможет сделать это правильно. И есть вероятность, что вы, вероятно, потратите еще больше времени на то, чтобы сделать это неправильно, прежде чем поймете это правильно. Я не знаю о вас, но большинству людей лучше делать свое время, нежели возиться целую вечность с одной и той же строкой кода.
Цао

@CHao - я не стал спорить о его достоинствах - я протестовал против его пропаганды.
mikeserv

16
@mikeserv: аргументы против этого обоснованы и заслужены. Даже вы показали им, чтобы быть правдой.
Цао

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, в результате которого будет отображаться глобус, ??поэтому сопоставляйте каждый 2-символьный файл в списке Как ни странно, если у вас есть другой файл \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

Заявленные намерения ОП

Предисловие и обоснование оригинального ответа обновлено 2015-05-18

В последнем обновлении своего вопроса mikeserv (ОП) заявил: «Я действительно считаю позором то, что впервые задал этот вопрос, чтобы указать на источник дезинформации, и, к сожалению, наиболее одобренный ответ здесь в значительной степени вводит в заблуждение. "

Ну ладно; Я чувствую, что это был довольно позор, что я потратил так много времени, пытаясь понять, как объяснить свое значение, только чтобы найти его , когда я перечитал вопрос. Этот вопрос закончил тем, что «[порождал] обсуждение, а не ответы» и в итоге занял примерно 18 КБ текста (только для вопроса, чтобы быть ясным), что было бы долго даже для сообщения в блоге.

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

На данный момент я не уверен, был ли мой ответ к сути или нет; возможно нет, но он был направлен на некоторые ваши вопросы, и, возможно, это может быть полезным ответом кому-то еще; начинающие отваживаются, некоторые из них «не превращаются» в «иногда делают», когда вы становитесь более опытным. :)

Как общее правило...

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

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

В оригинальной публикации несколько раз спрашивалось, почему в разных статьях даются советы, такие как «Не анализировать lsвывод» или «Никогда не анализировать lsвывод» и т. Д.

Мое предлагаемое решение проблемы заключается в том, что примеры такого рода утверждений являются просто примерами идиомы, сформулированной несколько иными способами, в которой абсолютный квантификатор сочетается с императивом [например, «не [никогда] X», «[Вы должны] всегда Y», «[не следует] никогда Z»], чтобы сформировать утверждения, предназначенные для использования в качестве общих правил или указаний, особенно когда они даны новичкам в предмете, а не предназначены для абсолютных истин, очевидная форма этих заявлений, несмотря на.

Когда вы начинаете изучать новый предмет, и если у вас нет четкого понимания того, почему вам, возможно, придется поступить иначе, хорошей идеей будет просто следовать принятым общим правилам без исключения - если только под руководством кого-то более опытного это сам. С повышением квалификации и опыта вы сможете в дальнейшем определять, когда и применяется ли правило в конкретной ситуации. Как только вы действительно достигнете значительного уровня опыта, вы, скорее всего, сначала поймете причины, лежащие в основе общего правила, и с этого момента вы сможете начать использовать свое суждение относительно того, применяются ли и на каком уровне причины, лежащие в основе правила, в эта ситуация, а также относительно того, есть ли, возможно, основные проблемы.

И именно тогда, возможно, эксперт может сделать что-то с нарушением «Правил». Но это не сделало бы их менее «Правилами».

И, поэтому, к данной теме: на мой взгляд, просто потому, что эксперт может нарушить это правило, не будучи полностью сбитым с толку, я не вижу способа, которым вы могли бы оправдать сообщение начинающего, что «иногда» это хорошо, чтобы разобрать lsвывод, потому что: это не так . Или, по крайней мере, конечно, для новичка это неправильно.

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

Правила, которые должны быть нарушены?

При чтении статьи на тему, которая предназначена или может быть прочитана новичками, часто вы увидите такие вещи:

  • «Вы не должны когда - либо делать X.»
  • "Никогда не делай Q!"
  • "Не делай Z."
  • «Нужно всегда делать Y!»
  • "C, несмотря ни на что."

Хотя эти утверждения, безусловно, утверждают абсолютные и неподвластные времени правила, это не так; вместо этого это способ формулирования общих правил [так называемых «руководящих принципов», «практических правил», «основ» и т. д.], по крайней мере, возможно, один из подходящих способов сформулировать их для начинающих, которые могут читать эти статьи. Тем не менее, только потому, что они заявлены как абсолютные, правила, безусловно, не связывают профессионалов и экспертов [которые, вероятно, были теми, кто суммировал такие правила в первую очередь, как способ записать и передать знания, полученные, когда они имели дело с повторяющимися проблемы в их конкретном ремесле.]

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

Модальное предложение

Странная особенность, по крайней мере в английском, и я полагаю, во многих других языках общих правил, заключается в том, что они изложены в той же форме, что и модальное предложение, однако эксперты в данной области готовы дать общее правило для ситуации, все время зная, что они нарушат правило, когда это уместно. Поэтому ясно, что эти утверждения не должны быть эквивалентны тем же утверждениям в модальной логике.

Вот почему я говорю, что они должны быть просто идиоматичными. Вместо того, чтобы действительно быть «никогда» или «всегда», эти правила обычно служат для кодификации общих руководящих принципов, которые, как правило, подходят в широком диапазоне ситуаций, и которые, когда начинающие следуют им слепо, могут привести к лучшие результаты, чем новичок, решивший пойти против них без веской причины. Иногда они кодифицируют правила, просто приводя к некачественным результатам, а не к прямым неудачам, сопровождающим неправильный выбор при нарушении правил.

Таким образом, общие правила не являются абсолютными модальными суждениями, которые они кажутся на поверхности, но вместо этого являются кратким способом дать правило с подразумеваемым стандартным образцом, что-то вроде следующего:

если у вас нет возможности сказать, что это руководство неверно в конкретном случае, и доказать себе, что вы правы, тогда $ {RULE}

где, конечно, вы могли бы заменить «никогда не анализировать lsвывод» вместо $ {RULE}. :)

О да! Как насчет синтаксического анализа lsвывода?

Ну, так что, учитывая все это ... я думаю, довольно ясно, что это правило хорошее. Прежде всего, настоящее правило следует понимать как идиоматическое, как объяснено выше ...

Но, кроме того, дело не только в том, что вам нужно хорошо разбираться в сценариях оболочки, чтобы знать, можно ли его сломать, в каком-то конкретном случае. Кроме того, требуется столько же умения, чтобы сказать, что вы ошиблись, когда пытаетесь сломать его при тестировании! И я уверенно говорю, что очень большая часть вероятной аудитории таких статей (давая советы типа «Не разбирай результат ls!») Не может делать такие вещи , и те, у кого есть такой навык, вероятно, поймут, что они понимают это самостоятельно и игнорируют правило так или иначе.

Но ... просто посмотрите на этот вопрос, и как даже люди, которые, вероятно, обладают навыком, думали, что это плохой призыв; и сколько усилий автор вопроса потратил только на то, чтобы добраться до точки текущего лучшего примера! Я гарантирую вам, что проблема сложная, 99% людей ошибаются и могут привести к очень плохим результатам! Даже если выбранный метод окажется хорошим; пока эта (или другая) lsидея синтаксического анализа не будет принята ИТ-специалистами / разработчиками в целом, выдержит много испытаний (особенно испытание временем) и, наконец, не сможет перейти к статусу «общей техники», вероятно, что Многие люди могут попробовать и ошибиться ... с катастрофическими последствиями.

Итак, еще раз повторю ... что, особенно в этом случае , вот почему " никогда не анализировать lsвывод!" это определенно правильный способ выразить это.

[ОБНОВЛЕНИЕ 2014-05-18: разъяснены причины ответа (выше), чтобы ответить на комментарий от ОП; следующее дополнение является ответом на дополнения ФП к вчерашнему вопросу]

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

† Первоначально я задумал это исключительно как пояснение к своему оригиналу; но определился с другими дополнениями после размышления

‡ см. Https://unix.stackexchange.com/tour для получения рекомендаций по сообщениям.


2
Никогда не идиоматичный. Это не ответ ни на что.
mikeserv

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

Глядя на ваш вопрос еще раз, я вижу, что вы сначала упоминаете «не ...», а не «никогда ...», что хорошо в вашем анализе, поэтому я также проясню этот момент. На данный момент уже есть решение первого типа, которое, по-видимому, продемонстрировано / объяснено к вашему удовлетворению, поэтому я не буду углубляться в это. Но я постараюсь уточнить свой ответ немного: как я уже сказал, я не пытался быть спорным (или конфронтационным!), Но чтобы указать, как эти заявления обычно предназначены.
шеллибаттерфляй

1
Я должен очистить этот пост. Тем не менее, никогда не является не правильным способом выражения его. Это немного смешно , что люди думают , что Theyre квалифицирован, чтобы сказать другим , никогда или DonT - просто сказать им , вы не думаете , он будет работать и почему, но вы знаете , что будет работать и почему. lsэто компьютерная утилита - вы можете анализировать вывод компьютера.
mikeserv

1
Ну, я поменял свое понижательное голосование, потому что, по крайней мере, ты прав насчет пометки. Я постараюсь почистить это сегодня или завтра. Я думаю, я перейду большинство примеров кода к ответу, я думаю. Но это все еще, насколько я обеспокоен, оправдывает неточности в этом часто цитируемом сообщении в блоге. Я бы хотел, чтобы люди вообще перестали ссылаться на руководство по bash - по крайней мере, до тех
пор,

16

Можно ли разобрать вывод ls в определенных случаях? Конечно. Идея извлечения списка номеров инодов из каталога является хорошим примером - если вы знаете, что ваша реализация lsподдерживает -q, и, следовательно, каждый файл будет выдавать ровно одну строку вывода, и все, что вам нужно, это номера инодов, анализируя их из 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аргументам, когда в имени файла есть новая строка? Это может выглядеть так, пока у вас не появятся 2 разных файла, которые содержат непечатаемый символ в одном месте в имени файла, а затем lsвывод не позволит вам определить, какой из них был самым большим. Хуже того, чтобы расширить «?», Он, вероятно, прибегает к своей оболочке eval- что вызовет проблемы, если он попадет в файл с именем, например,

foo`/tmp/malicious_script`bar

Помогает ли --quoting-style=shell(если ваша lsдаже поддерживает)? Нет, все еще показывает? для непечатаемых символов, так что все еще неоднозначно, какое из нескольких совпадений было самым большим. --quoting-style=literal? Нет, то же самое. --quoting-style=localeили же--quoting-style=c может помочь, если вам просто нужно однозначно напечатать имя самого большого файла, но, вероятно, нет, если вам нужно потом что-то сделать с файлом - это будет куча кода, чтобы отменить кавычки и вернуться к реальному имени файла, так что вы можете передать его, скажем, GZIP.

И в конце всей этой работы, даже если то, что у него есть, является безопасным и правильным для всех возможных имен файлов, это нечитабельно и не поддерживается, и его можно было бы сделать намного проще, безопаснее и читабельнее в python, perl или ruby.

Или даже используя другие инструменты оболочки - вне головы, я думаю, что это должно сработать:

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

И должен быть как минимум таким же портативным, как --quoting-styleесть.


О, правда насчет размера - я, вероятно, мог бы сделать это, если бы попытался - не так ли? Я вроде устал или все это - мне нравится твой ответ, потому что ты не говоришь, не можешь или не хочешь или никогда, но на самом деле приводите примеры, может быть, почему бы и нет, и сравнимо, как иначе - спасибо.
mikeserv

Я думаю, что если бы вы попытались, вы бы обнаружили, что это гораздо сложнее, чем вы думаете. Так что да, я бы порекомендовал попробовать. Я буду рад дать вам имена файлов, которые будут вам недоступны, пока я буду думать о них. :)
godlygeek

Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Тердон

@mikeserv и godlygeek, я переместил эту ветку комментариев в чат . Пожалуйста, не проводите долгих обсуждений, подобных этому, в комментариях, для этого и нужен чат.
Тердон
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.