Учитывая эти имена файлов:
$ ls -1
file
file name
otherfile
bash
само по себе прекрасно работает со встроенными пробелами:
$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?
Однако иногда я могу не захотеть работать с каждым файлом или даже строго в том месте $PWD
, где он find
есть. Который также обрабатывает пробельные символы номинально:
$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name
Я пытаюсь придумать версию этого скриплета, безопасную для свободного пространства, которая возьмет выходные данные find
и представит их в select
:
$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file
Тем не менее, это взрывается с пробелами в именах файлов:
$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file 3) name 5) ./directory/file
2) ./file 4) ./directory/file 6) name
Обычно я бы обошел это, возиться с IFS
. Тем не мение:
$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
Какое решение это?
bash
text-processing
whitespace
select
DopeGhoti
источник
источник
find
своей способности соответствовать определенное имя файла, вы можете просто использоватьselect file in **/file*
(после установкиshopt -s globstar
) вbash
4 или более поздней версии.Ответы:
Если вам нужно обрабатывать только пробелы и символы табуляции (не вставлять символы новой строки), вы можете использовать
mapfile
(или его синонимreadarray
) для чтения в массив, например, заданныйтогда
Если делать нужно обрабатывать переводы строк, и ваша
bash
версия обеспечивает нуль-разделителиmapfile
1 , то вы можете изменить чтоIFS= mapfile -t -d '' files < <(find . -type f -print0)
. В противном случае, собрать эквивалентный массив изfind
вывода с разделением нулями, используяread
цикл:1
-d
опция была добавлена кmapfile
вbash
версии 4.4 IIRCисточник
mapfile
это новый для меня тоже. Престижность.while IFS= read
Версия работает еще в Баш v3 (что очень важно для тех , кто из нас с помощью MacOS).find -print0
вариант; ворчание за то, что оно было написано после известной неверной версии и описано только для использования, если известно, что им нужно обрабатывать переводы строки. Если человек обрабатывает неожиданное только в тех местах, где его ожидают, он никогда не будет обрабатывать неожиданное вообще.Этот ответ имеет решения для любого типа файлов. С переводом строки или пробелами.
Есть решения для недавнего bash, а также древнего bash и даже старых posix-оболочек.
Дерево, перечисленное ниже в этом ответе [1] , используется для тестов.
Выбрать
select
Работать с массивом легко :Или с позиционными параметрами:
Таким образом, единственной реальной проблемой является получение «списка файлов» (правильно разделенных) внутри массива или внутри Позиционных Параметров. Продолжай читать.
удар
Я не вижу проблемы, о которой вы сообщаете с bash. Bash может искать внутри заданного каталога:
Или, если вам нравится цикл:
Обратите внимание, что приведенный выше синтаксис будет работать правильно с любой (разумной) оболочкой (по крайней мере, не csh).
Единственный предел, который имеет синтаксис выше, это спуск в другие каталоги.
Но Bash может сделать это:
Чтобы выбрать только некоторые файлы (например, те, которые заканчиваются на файле), просто замените *:
крепкий
Когда вы поместите в заголовок « безопасный для космоса », я буду предполагать, что вы имели в виду « надежный ».
Самый простой способ быть уверенным в пробелах (или символах новой строки) - отказаться от обработки ввода, в котором есть пробелы (или символы новой строки). Очень простой способ сделать это в оболочке - это выйти с ошибкой, если имя какого-либо файла расширяется пробелом. Есть несколько способов сделать это, но самый компактный (и posix) (но ограниченный одним содержимым каталога, включая имена вспомогательных каталогов и избегая точечных файлов):
Если используемое решение является надежным в любом из этих пунктов, удалите тест.
В bash подкаталоги можно было проверить сразу с помощью **, описанного выше.
Есть несколько способов включить точечные файлы, решение Posix:
находить
Если по какой-либо причине необходимо использовать find, замените разделитель на NUL (0x00).
Баш 4.4+
Баш 2.05+
POSIXLY
Чтобы сделать правильное решение POSIX, где find не имеет разделителя NUL и нет
-d
(или-a
) для чтения, нам нужен совершенно иной подход.Нам нужно использовать комплекс
-exec
от find с вызовом оболочки:Или, если вам нужен выбор (select - это часть bash, а не sh):
[1] Это дерево (\ 012 - новые строки):
Может быть построен с помощью этих двух команд:
источник
Вы не можете установить переменную перед циклической конструкцией, но вы можете установить ее перед условием. Вот сегмент со страницы руководства:
(Цикл не простая команда .)
Вот часто используемая конструкция, демонстрирующая сценарии неудачи и успеха:
К сожалению, я не вижу способа внедрить измененное
IFS
вselect
конструкцию, в то время как это влияет на обработку связанного$(...)
. Тем не менее, ничто не мешаетIFS
быть установленным вне цикла:и именно эта конструкция, которую я вижу, работает с
select
:При написании оборонительного кода , который я рекомендовал бы , что положение либо работать в субоболочке, или
IFS
иSHELLOPTS
сохранено и восстановлено вокруг блока:источник
IFS=$'\n'
это безопасно, необоснованно. Имена файлов прекрасно могут содержать литералы новой строки.[0-9a-f]{24}
. ТБ резервных копий данных, использованных для поддержки счетов клиентов, были потеряны.select
По своей сути он предназначен для решения на основе сценариев , поэтому он всегда должен быть разработан для обработки крайних случаев.select
из оболочки, где вы вводите команды для запуска, но только из сценария, когда вы отвечаете на приглашение, предоставленное этим сценарием , и где этот сценарий находится выполнение предопределенной логики (созданной без знания имен файлов, с которыми ведется работа) на основе этого ввода.Я могу быть вне моей юрисдикции здесь, но, возможно, вы можете начать с чего-то вроде этого, по крайней мере, это не имеет никаких проблем с пробелами:
Чтобы избежать любых возможных ложных предположений, как отмечено в комментариях, имейте в виду, что приведенный выше код эквивалентен:
источник
read -d
это умное решение; Спасибо за это.read -d $'\000'
это точно идентичноread -d ''
, но вводит в заблуждение людей о возможностях в Bash (подразумевая, неправильно, что это может представлять буквальные NULs внутри строк). Запуститеs1=$'foo\000bar'; s2='foo'
, а затем попытайтесь найти способ различения двух значений. (Будущая версия может нормализоваться с поведением подстановки команд, делая сохраненное значение эквивалентнымfoobar
, но сегодня это не так).