пусть есть неопределённое количество путей к файлам или каталогам которое я получаю например от zenity. В путях могут быть пробелы. Мне нужно все эти пути передать какой либо команде, принимающей несколько путей разделённых пробелами.
Передавать придётся через переменную, как мне в неё записать эти пути, чтобы они верно передались?
вариант c заворачиванием путей в одинарные кавычки и всего этого в двойные кавычки
p=" '/п 1/ф 1' '/п 2/ф 2' "
ls $p
не прокатывает.
Как ещё можно завернуть пути с пробелами?
> p=" '/п 1/ф 1' '/п 2/ф 2' "
> ls $p
> не прокатывает.
> Как ещё можно завернуть пути с пробелами?Пусть пробел будет разделителем слов, а перевод строки разделителем путей. Понадеемся что у вас вас нет файлов с переводом строки в имени.
Пути храним в переменной, по одному передаём их команде.IFS='' p="/п 1/ф 1
/п 2/ф 2
/п 3/ф 3"
echo $p | while read FILEPATH; do ls "$FILEPATH"; done
(Итерировать, наверное, можно как-то покрасивше.)
Пусть пробел будет разделителем слов, а символ ° разделителем путей. Понадеемся что у вас вас нет файлов с переводом "°" в имени.
Пути храним в переменной, передаём их команде все вместе.IFS='°' p="/п 1/ф 1°/п 2/ф 2°/п 3/ф 3"
IFS='°' ls $p
Что такое IFS:
https://bash.cyberciti.biz/guide/$IFS
https://unix.stackexchange.com/questions/16192/what-is-the-ifs
> Понадеемся что у вас вас нет файлов с переводом строки в имени.А такое вообще возможно?
>> Понадеемся что у вас вас нет файлов с переводом строки в имени.
> А такое вообще возможно?Увы.
Я не пробовал, но https://unix.stackexchange.com/questions/118959/how-to-find-...
>>> Понадеемся что у вас вас нет файлов с переводом строки в имени.
>> А такое вообще возможно?
> Увы.
> Я не пробовал, но https://unix.stackexchange.com/questions/118959/how-to-find-...Н-да, вот в такие моменты понимаешь что на самом деле незнаешь нихрена %)
> А такое вообще возможно?Возможно! Буквально неделю назад сделал ошибку в скрипте и он мне генерил каталог для лог-файлов с двухэтажным названием х) Файловый менеджер Caja даже корректно это всё отображал - каталог и под ним 2 строчки названия)
Спасибо большое. Остановился на таком варианте:
IFS='
'
path=`zenity --file-selection --directory --multiple --separator='
' --title="Выбери каталоги. (Переносы строки в именах файлов не поддерживаются!)"`
# ВНИМАНИЕ! Взятие $path в двойные кавычки недопустимо, из за IFS
do_some $path
Да, вот по непонятным мне причинам теперь если выполнять действие со взятием переменной с путями в кавычки то ничего не работает. Но это вроде бы не проблема. Можно ведь и не брать. Ещё из непонятного - это почему то не прокатывает указывать разделитель как '\n' а не как:
'
'
Вроде у zenity с этим проблемы, но не только у него. Я когда так делал без zenity и в конце выполнял ls $path у меня почему-то ls кроме указанных каталогов пытался ещё показать катлог под названием '' то есть пустая строка.
> --title="Выбери каталоги. (Переносы строки в именах файлов не поддерживаются!)"Прямо так с восклицательным знаком и работает? В Баше?
Чем дефолтный "|" в качестве сепаратора не устраивает? Гораздо более читабельно всё будет.
IFS имеет смысл задавать для конкетной команды, чтобы он не загрязнял дефолт для остального скрипта.
Не так:
IFS='|'
...
do_some $pathА так:
...
IFS='|' do_some $path> указывать разделитель как '\n'
Так можно, но мудрить надо, навскидку не разберусь.
> Прямо так с восклицательным знаком и работает? В Баше?Ну да. В первой строке скрипта: "#!/bin/bash", запускается на Ubuntu Studio 19.10 в xfce4-terminal 0.8.8.
А где такое не должно работать?
> Чем дефолтный "|" в качестве сепаратора не устраивает? Гораздо более читабельно всё
> будет.Читабельнее-то будет, но я тут недавно узанал... Вернее не смог узнать существует ли в принципе в природе символ который ext4 не позволил бы мне засунуть в имя, так что я был не хило озадачен вопросом, а что мне вообще тогда в качестве разделителя использовать. Пришёл к выводу, что перенос строки - самый лучший вариант, так как самому мне в голову не придёт его в имени файла юзать, да и никогда не встречал чтобы кто-то другой его туда вставлял, к тому же двухэтажные имена легко замечаются в ФМ и я их в случае чего легко выловлю и почищу от переносов.
> IFS имеет смысл задавать для конкетной команды, чтобы он не загрязнял дефолт
> для остального скрипта.Знаете, сперва бросился исправлять, а пртом протестил и пришёл к выводу что такая форма записи тоже загрязняет дефолт, либо я чего-то не понимаю. Вот проведённый эксперимент:
i@MediaLab1:~$ IFS='|'
i@MediaLab1:~$ echo "$IFS"
|
i@MediaLab1:~$ IFS=':' p='dd'
i@MediaLab1:~$ echo "$IFS"
:
>> Прямо так с восклицательным знаком и работает? В Баше?
> Ну да. В первой строке скрипта: "#!/bin/bash", запускается на Ubuntu Studio 19.10
> в xfce4-terminal 0.8.8.
> А где такое не должно работать?Баш интерпретирует восклицательный знак в последовательностях и его обычно приходится экранировать.
https://www.itworld.com/article/2717119/linux-tip--using-an-...
>[оверквотинг удален]
> такая форма записи тоже загрязняет дефолт, либо я чего-то не понимаю.
> Вот проведённый эксперимент:
>
> i@MediaLab1:~$ IFS='|'
> i@MediaLab1:~$ echo "$IFS"
> |
> i@MediaLab1:~$ IFS=':' p='dd'
> i@MediaLab1:~$ echo "$IFS"
> :
>Сравните:
LANG=C date
echo $LANG
С
LANG=C
date
echo $LANG
Вывод date будет один и тот-же, на языке соответствущем LANG. В первом случае, после исполнения date, значение LANG останется на дефолте, а во втором изменится.Работает в строчках которые запускают команды. В строчках которые присваивают значения, там странно.
Разумеется, делайте как вам будет удобнее.
А зачем внутри одного скрипта плодить несколько вариантов IFS (да и любых других переменных тоже)? После завершения работы скрипта оболочка всеравно вернётся к дефолту (ес-сно если не юзать export).
> А зачем внутри одного скрипта плодить несколько вариантов IFS (да и любых
> других переменных тоже)? После завершения работы скрипта оболочка всеравно вернётся к
> дефолту (ес-сно если не юзать export).Если скрипт небольшой, то пофигу.
А если большой, то имеет смысл стараться держать окружение максимально близким к дефолту, держа под контролем все отклонения, и возвращаясь к дефолту при первой возможности. Дабы меньше дебажить странные глюки.
Собственно, это не догма, а практическое соображение. Если оно облегчает жизнь, то надо следовать. Если нет, значит на то причина есть.
>> А зачем внутри одного скрипта плодить несколько вариантов IFS (да и любых
>> других переменных тоже)? После завершения работы скрипта оболочка всеравно вернётся к
>> дефолту (ес-сно если не юзать export).
> Если скрипт небольшой, то пофигу.
> А если большой, то имеет смысл стараться держать окружение максимально близким к
> дефолту, держа под контролем все отклонения, и возвращаясь к дефолту при
> первой возможности. Дабы меньше дебажить странные глюки.
> Собственно, это не догма, а практическое соображение. Если оно облегчает жизнь, то
> надо следовать. Если нет, значит на то причина есть.Имхо, в рамках одного скрипта проще такое делать для всего скрипта. Т.е установить переменную в самом начале и помнить что в этом скрипте IFS это перенос строки, а не пробел, а не сидеть потом через пару сотен строк и не офигевать почему оно вдруг работает не так как ожидалось, а оказывается мы где-то там забыли сбросить переменную до пробела. Оно вроди и мелочь, но на отладке такого потом глаза могут повылезать пока найдёшь %)
> Работает в строчках которые запускают команды. В строчках которые присваивают значения,
> там странно.Из за этой странности не изменять IFS глобально не получится. Провёл много проб и понял, что пока не сделаешь так:
IFS=$'\n' p='путь
путь
путь'
IFS=$'\n' ls $p
ничего не заработает. То есть в любом случае при задании переменной прихоидится указывать IFS=$'\n' а раз уж это всё равно глобально изменит IFS то смысла особого в этом нет.Очень всё это странно, потому что если менять IFS на весь скрипт и не парится, то можно задавать переменную с путём даже до изменения IFS.
Ta там странностей немерено:
Тот вариант с массивами, что человек ниже предложил:
Если делать вот такdeclare -a array=("c 1" "c 2")
To всё работает и соответственно если сделатьfor i in ${array[@]}; do
echo $i
done
На выхлопе получимc 1
c 2
Но если сделатьdeclare -a array=("$(zenity --file-selection --multiple --separator='" "')")
To в массив попадёт только один вот такой элемент:"c 1" "c 2"
Т.е если это писать руками всё норм, а если делать подстановку - весь выхлоп пишется как один элемент массива, хотя выхлоп подстановки соответствует тому что пишется руками и должен быть воспринят как несколько элементов. Но нет, оно его тащит как строку и хоть ты тресни %)
> непонятного - это почему то не прокатывает указывать разделитель как '\n'
IFS=$'\n'
>> непонятного - это почему то не прокатывает указывать разделитель как '\n'
>IFS=$'\n'Решил проверить, начав с самого рабочего варианта и теперь такое впечатление что теперь всё стало работать совсем не так. Хоть убей не вижу своей ошибки:
i@MediaLab1:/media/i/Tmp/ScriptTestingPOLYGON$ IFS='
> 'i@MediaLab1:/media/i/Tmp/ScriptTestingPOLYGON$ ls 'c 1
> c 2'ls: невозможно получить доступ к 'c 1'$'\n''c 2': Нет такого файла или каталога
Господи, откуда он символ доллара в пути взял...
Потому что не ls 'c 1 c 2', a ls "c 1" "c 2" или 'c 1' 'c 2'.
Конструкция $'\n' это и есть перенос строки.В варианте
'c 1оно какраз пытается найти двухэтажное имя.
c 2'
т.е:'c 1Т.е в кавычки либо не брать вообще, либо имя каждого каталога отдельно.
c 2' == 'c 1'$'\n''c 2'a
'c 1'
'c 2' == '"c 1"
"c 2"'
[ diablopc@d200 ~/test ]$ cat test.sh
#!/bin/bash
IFS=$'\n'
SELECTION=$(zenity --file-selection --directory --multiple --separator="$IFS" --title="olololo")
ls $SELECTION
[ diablopc@d200 ~/test ]$ ./test.sh
'/home/diablopc/test/c 1':
1_1 1_2'/home/diablopc/test/c 2':
2_1 2_2
> Конструкция $'\n' это и есть перенос строки.Всё! Вот чего я не понимал в контексте проблемы с IFS='\n'. Что \n заменяется на перенос строки только в одинарных кавычках и только если перед ними стоит $
>> Конструкция $'\n' это и есть перенос строки.
> Всё! Вот чего я не понимал в контексте проблемы с IFS='\n'. Что
> \n заменяется на перенос строки только в одинарных кавычках и только
> если перед ними стоит $Агась. А ниже вон человек самый правильный вариант для этого всего предложил. И IFS трогать ненужно и, по идее, там даже переносы строк не помеха.
> И IFS трогать ненужно и, по идее, там даже переносы строк
> не помеха.Да. Кавычки расставлены так, что при разворачивании всё оказывается целыми строками, независимо от наличия IFS или других специальных символов внутри значений. Различие между @ и * при развёртке массива.
Не трогая IFS можно вот так (Zenity заменена на find, для примера):
while read filename ; do
echo "I found name: '${filename}'"
done <<< "$( find /tmp -maxdepth 1 )"В зависимости от реализации сам(и) IFS можно тронуть, если явно удобно. Если каждая строка это реально отдельный элемент массива (что не всегда), то можно вот так, с бэкапом IFS, если нужен:
Синтаксис описан здесь: https://wiki.bash-hackers.org/syntax/arrays
#!/bin/bashmkdir "/tmp/asd fgh" "/tmp/qwe rty"
IFS_back="${IFS}"
IFS=$'\n'
declare -a strings_array=($( ls -1 /tmp ))
for single_string in "${strings_array[@]}" ; do
if [[ "${single_string}" =~ ^"asd".*|.*" rty"$ ]] ; then # Прячу прочее содержимое своего /tmp, показываю только нужные имена.
echo $single_string
fi
done
IFS="${IFS_back}"
В зависимости от идеи бывает нужно сохранить для обработки позже. Причём IFS не трогается. Но больше вычислений - медленнее. Можно так:
declare -a str_array=()
while read filename ; do
str_array+=("${filename}")
done <<< "$( find /tmp -maxdepth 1 )"
echo "${str_array[0]}"
echo "${str_array[1]}"
echo "Total found: ${#str_array[@]}"Либо ещё сильнее:
declare -A str_map=()
while read path ; do
str_map[$path]=$( basename "${path}" )
done <<< "$( find /tmp -maxdepth 1 )"for dir_name in "${!str_map[@]}" ; do
echo "Have file '${str_map[$dir_name]}' in '$(dirname "${dir_name}")'"
done
echo "Total found: ${#str_map[@]}"Иногда по логике полезно применять логгирование и свал скрипта на первом возврате ошибки (рекомендации от Debian), объявления переменных readonly. Это позволяет гораздо меньше морочиться обработкой ошибок и быстрее их находить.
#!/bin/bash
PS4="+:$( basname \"\${0}\" ):\${LINENO}: "
set -xeu -o pipefail
declare -r some_name="asdfgh"Применяется всё это примерно вот так:
#!/bin/bashPS4="+:$( basename \"\${0}\" ):\${LINENO}: "
set -xeu -o pipefailfunction do_did_done {
local -A str_map
while read path ; do
str_map[$path]=$( basename "${path}" )
done <<< "$( find /tmp -maxdepth 1 )"
total_found="${#str_map[@]}"
}total_found=0
do_did_doneset +e # Выключил свал.
false # Сгенерировал код возврата ошибка, а оно не падает.echo "ИНФОРМАЦИЯ:${0}:${LINENO}: Total found ${total_found}" # Не залоггировало саму команду т.к. set +x
set -e # Включил свал обратно. Осторожно, при определённых условиях внутрь функции не наследуется.
set -x # Чтобы показало, на чём оно упадёт
declare -r dir_name="никогда-не-было-такой-директории"
test -d "${dir_name}" # А вот тут упало, с очень показательной диагностикой, т.к. set -e !!!echo "Сюда уже не дойдёт."
По причине '<<<' - это Bash код.
По причине "declare -A" - это версия Bash 4+До встречи в продакшн!! :)
P.S. Отвечая на старинный вопрос: зачем Вы используете эти башизмы '<<<'? Т.к. при таком приёме переменная, объявленная внутри цикла, сохранится по окончании цикла. Спасибо Opennet, здесь ведь научился.
#!/bin/bash
declare -a strings_array=("asd fgh" "qwe rty")
for single_string in "${strings_array[@]}" ; do
echo "$single_string"
done
#!/bin/bash
declare -a strings_array=("asd fgh" "qwe rty")
cd /tmp
mkdir "${strings_array[@]}"
ls -ld "/tmp/asd fgh"
ls -ld "/tmp/qwe rty"
Люто плюсую!
Массивы же зачем-то существуют))))