Создание надежных путей

Вопрос или проблема

Предположим, у меня есть несколько переменных в 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-системами, и это позволяет в какой-то степени расслабить строгость формирования путей. Тем не менее, эстетичная и чистая структура путей остаётся важной практикой для поддерживаемости и понятности кода, особенно если он используется в составе более крупных систем и проектов.

Оцените материал
Добавить комментарий

Капча загружается...