Я постоянно вижу ответы, цитирующие эту ссылку с указанием "Не разбирайся ls
!" Это беспокоит меня по нескольким причинам:
Кажется, что информация в этой ссылке была принята оптом с небольшим вопросом, хотя я могу выделить по крайней мере несколько ошибок при случайном чтении.
Также кажется, что проблемы, указанные в этой ссылке, не вызвали желания найти решение.
Из первого абзаца:
... когда вы запрашиваете
[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.
stat
в своем ответе, поскольку он фактически проверяет, существует ли каждый файл. Ваш бит внизу с sed
вещью не работает.
ls
? То, что вы описываете, очень сложно. Мне нужно разобрать его, чтобы понять все это, и я относительно компетентный пользователь. Вы не можете ожидать, что ваш средний Джо сможет справиться с чем-то вроде этого.
ls
вывод синтаксического анализа является неправильным, были хорошо освещены в исходной ссылке (и во многих других местах). Этот вопрос был бы разумным, если бы ОП просил помочь понять его, но вместо этого ОП просто пытается доказать, что его неправильное использование в порядке.
parsing ls is bad
. Делать for something in $(command)
и полагаться на разделение слов для получения точных результатов плохо для большинства из command's
них, у которых нет простого вывода.
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 с