Вопрос или проблема
Предположим, у меня есть несколько переменных в shell-скрипте (например, в zsh):
FOLDER_1, FOLDER_2 и т.д.
Эти переменные относятся к папкам, которые находятся по иерархии, начиная с /
. Например, если у меня есть путь /home/me/stuff/items
переменные будут такими:
FOLDER_1='home'
FOLDER_2='me'
FOLDER_3='stuff'
Теперь, предположим, что я хочу восстановить соответствующий путь, конкатенируя переменные. Один из возможных способов сделать это так:
PATH=$FOLDER_1/$FOLDER_2/$FOLDER_3/
Однако, предположим, что некоторые переменные FOLDER_i
содержат завершающие прямые слэши, а другие нет (и мы не знаем, какие именно), например:
FOLDER_1='home'
FOLDER_2='stuff/'
FOLDER_3='items'
Вопрос: Как я могу построить путь устойчиво? (например, избегая двойных слэш и добавляя их там, где это необходимо).
Я подумал, что один из способов сделать это – добавлять /
всегда между парами переменных, а затем удалять дублирование с помощью sed
, но это не получается (я не уверен, правильно ли я обрабатываю /
в sed
).
Также, не изобретаю ли я велосипед? (т.е. есть ли уже встроенные средства, которые это делают?).
Наконец, если переменные находятся в массиве, например, FOLDERS
, будет ли возможно сделать это без цикла? (или альтернативно, с циклом, но не зная, сколько FOLDERS
в массиве).
Простой ответ – перестать волноваться и полюбить множественные слэши. Множественные слэши имеют тот же эффект, что и одинарный слэш (за исключением того, что путь, начинающийся с //
, имеет другой смысл на нескольких системах; только в слоях эмуляции UNIX на Windows я могу их назвать). Это сделано преднамеренно, и возможность собирать имена файлов, не беспокоясь о множественных слэшах, была главной частью этого решения.
Чтобы соединить элементы массива в zsh, вы можете использовать параметр j
флаг расширения параметра.
dir=${(j:/:)FOLDERS}
Вы можете сжать дублирующиеся слэши сразу, но это исключительно косметика.
setopt extended_glob
dir=${${(j:/:)FOLDERS}//\/\/##/\/}
В ksh и bash вы можете соединить массив, используя первый символ $IFS
в качестве разделителя. Затем можно удалить дублирующиеся слэши. В bash вам нужно выполнить shopt -s extglob
, чтобы включить шаблоны ksh в последней строке приведенного ниже фрагмента.
IFS=/
dir="${FOLDERS[*]}"
unset IFS
dir=${dir//\/+(\/)//}
Вы можете использовать printf
с массивом:
parts=("$FOLDER_1" "$FOLDER_2" "$FOLDER_3");
printf '/%s' "${parts[@]%/}"
Оператор %
обрезает завершающие строки, в данном случае /
. Применение его к parts[@]
обрезает каждый элемент массива отдельно.
Ключевым для понимания этого трюка с printf
является следующая часть из man 1 printf
: “Форматная строка повторяется так часто, как необходимо для соответствия аргументам.”
Если вы хотите сохранить результат в переменной, используйте printf -v ваше_имя_переменной
:
parts=("$FOLDER_1" "$FOLDER_2" "$FOLDER_3");
printf -v full_path '/%s' "${parts[@]%/}"
#echo "$full_path" # чтобы посмотреть результат
Как именно sed
не работает для вас? Попробуйте sed 's|/\+"https://unix.stackexchange.com/"g'
после объединения или sed 's"https://unix.stackexchange.com/"|g'
перед.
bash 4
ввел расширенное глобы, которое позволяет использовать регулярные выражения в подстановке переменных ${var....}
… По умолчанию оно отключено. Чтобы включить его для вашего скрипта, просто установите опцию оболочки extglob…
Предполагается, что $dir == /home/me/////////stuff//items
shopt -s extglob; dir="${dir//+(\/)//}"
Результирующее значение $dir
/home/me/stuff/items
Вот несколько примеров того, что делать и чего не делать — Bash Extended Globbing
Используя Raku (известный ранее как Perl6)
Используя рутину cleanup
в Raku (для объектов IO::Path
):
~$ echo "/home/me/////////stuff//items" | raku -ne '.IO.cleanup.say;'
"/home/me/stuff/items".IO
~$ echo "/home/me/////////stuff//items" | raku -ne '.IO.cleanup.put;'
/home/me/stuff/items
Или, удаляя последовательные /
с помощью регулярного выражения Raku:
~$ echo "/home/me/////////stuff//items" | raku -ne '.subst(:global, / \/**2..* % <same> /, "/").IO.say;'
"/home/me/stuff/items".IO
~$ echo "/home/me/////////stuff//items" | raku -ne '.subst(:global, / \/**2..* % <same> /, "/").IO.put;'
/home/me/stuff/items
Выше приведены ответы на Raku, члене семейства языков программирования Perl. Raku обладает кросс-платформенными объектами IO
, которые могут создавать/разбирать пути для вас.
Простое создание и разрешение пути (используйте put
, чтобы вернуть только путь):
~$ % raku -e 'my $foo = IO::Path.new("foo", :CWD</Users/admin>); $foo.say;
$foo.=add(<bar baz>); $foo.say;
$foo.=resolve.relative; $foo.say;'
"foo".IO
"foo/bar/baz".IO
"/Users/admin/foo/bar/baz".IO
Разбиение пути на parent
и basename
:
~$ echo "/home/me/stuff/items" | raku -ne '.IO.parent.say;'
"/home/me/stuff".IO
~$ echo "/home/me/stuff/items" | raku -ne '.IO.basename.say;'
items
См. страницу RakuDocs class IO::Path для информации о том, как работать с томами, расширениями и т.д. И да (для любопытствующих), Raku будет принимать несколько последовательных /
как часть валидного пути, без cleanup
(не было проведено обширное тестирование – работает с заданным примером).
https://docs.raku.org/type/IO/Path
https://stackoverflow.com/a/69489459
https://raku.org
Ответ или решение
Работа с файловыми путями в скриптах оболочки, таких как zsh
или bash
, может представлять собой определенные сложности, особенно когда вы имеете дело с переменными, содержащими пути к директориям, которые могут включать или не включать завершающие слэши. Это часто приводит к возникновению избыточных слешей или их отсутствию в нужных местах, что может вызвать ошибки при выполнении скриптов. В этой статье мы подробно рассмотрим, как создать надежный и устойчивый к ошибкам подход для создания путей и проверим, какие средства предоставляет язык для решения этой задачи.
Теоретическая основа
При работе с файловыми системами в Unix-подобных системах использование нескольких последовательных слешей (/
) в пути считается допустимым и системой воспринимается как один слеш. Это означает, что /home/me/stuff
, /home//me/stuff
и даже /home///me/stuff
будут интерпретироваться как одно и то же расположение. Однако, для эстетических и поддерживаемых причин, всё же желательно минимизировать количество избыточных слешей в пути.
В языках Unix-оболочек, таких как zsh
и bash
, существует несколько подходов для конкатенации строк, которые в совокупности могут помочь избежать избыточных слешей без использования внешних инструментов, таких как sed
. Однако, прежде чем приступить к программным решениям, важно понять и принять тот факт, что путь с несколькими слешами, по сути, действует так же, как и с одним, и это встроенное поведение можно использовать в своих интересах.
Пример использования и объяснение
Zsh: Чтобы объединить несколько переменных в один путь, можно воспользоваться флагом расширения параметра j
. Он позволяет объединить элементы массива через произвольный символ, например, через слеш.
FOLDERS=(home me stuff)
dir=${(j:/:)FOLDERS}
echo "$dir" # Вывод: home/me/stuff
Здесь используется j:/:
для объединения элементов массива через /
. Для удаления избыточных слешей можно воспользоваться расширением, предварительно активировав расширенное глобальное соответствие:
setopt extended_glob
dir=${${(j:/:)FOLDERS}//\/\/##/\/}
echo "$dir" # Вывод: home/me/stuff
Bash: В bash
можно использовать переменную окружения IFS
для указания символа-разделителя при объединении элементов массива. Затем расширенная глобальная замена может быть использована для удаления избыточных слешей.
FOLDERS=("home" "me" "stuff")
IFS=/
dir="${FOLDERS[*]}"
unset IFS
shopt -s extglob
dir=${dir//\/+(\/)//}
echo "$dir" # Вывод: home/me/stuff
Применение
Если задаться целью сделать управление путями более универсальным и удобочитаемым, чтобы облегчить поддержание и отладку скриптов, вышеописанные подходы в zsh
и bash
можно дополнить использованием встроенной команды printf
. Это часто считается наиболее чистым решением, особенно если важно сохранить поведение для каждой очереди символов.
parts=("home" "me" "stuff")
printf -v full_path '/%s' "${parts[@]%/}"
echo "$full_path" # Вывод: /home/me/stuff
Этот подход использует форматированную строку для последовательного вывода элементов с добавлением слеша перед каждым элементом, исключая избыточные завершающие слеши для чистого объединения.
Заключение
Создание надежных путей в скриптах оболочек требует понимания работы с строками и особенностей файловых систем. Важно использовать особенности языка, такие как расширенные механизмы замены и работы с массивами, чтобы избежать классических ошибок и сократить количество внешних зависимостей.
При всех деталях реализации следует помнить, что многократные слеши в путях не нарушают их работы благодаря особенностям обработки Unix-системами, и это позволяет в какой-то степени расслабить строгость формирования путей. Тем не менее, эстетичная и чистая структура путей остаётся важной практикой для поддерживаемости и понятности кода, особенно если он используется в составе более крупных систем и проектов.