The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]




Версия для распечатки Пред. тема | След. тема
Новые ответы [ Отслеживать ]
Как передать несколько путей с пробелами через переменную?, !*! inFlowiaLab, 28-Май-20, 19:11  [смотреть все]
пусть есть неопределённое количество путей к файлам или каталогам которое я получаю например от zenity. В путях могут быть пробелы. Мне нужно все эти пути передать какой либо команде, принимающей несколько путей разделённых пробелами.
Передавать придётся через переменную, как мне в неё записать эти пути, чтобы они верно передались?
вариант c заворачиванием путей в одинарные кавычки и всего этого в двойные кавычки
p=" '/п 1/ф 1' '/п 2/ф 2' "
ls $p
не прокатывает.
Как ещё можно завернуть пути с пробелами?
  • Как передать несколько путей с пробелами через переменную?, !*! Licha Morada, 20:50 , 28-Май-20 (1) +2
    > 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

    • Как передать несколько путей с пробелами через переменную?, !*! DiabloPC, 03:14 , 29-Май-20 (2)
      > Понадеемся что у вас вас нет файлов с переводом строки в имени.

      А такое вообще возможно?

    • Как передать несколько путей с пробелами через переменную?, !*! inFlowiaLab, 22:23 , 29-Май-20 (6)
      Спасибо большое. Остановился на таком варианте:

      IFS='
      '
      path=`zenity --file-selection --directory --multiple --separator='
      ' --title="Выбери каталоги. (Переносы строки в именах файлов не поддерживаются!)"`
      # ВНИМАНИЕ! Взятие $path в двойные кавычки недопустимо, из за IFS
      do_some $path

      Да, вот по непонятным мне причинам теперь если выполнять действие со взятием переменной с путями в кавычки то ничего не работает. Но это вроде бы не проблема. Можно ведь и не брать. Ещё из непонятного - это почему то не прокатывает указывать разделитель как '\n' а не как:
      '
      '
      Вроде у zenity с этим проблемы, но не только у него. Я когда так делал без zenity и в конце выполнял ls $path у меня почему-то ls кроме указанных каталогов пытался ещё показать катлог под названием '' то есть пустая строка.
      • Как передать несколько путей с пробелами через переменную?, !*! Licha Morada, 23:15 , 29-Май-20 (7) +1
        > --title="Выбери каталоги. (Переносы строки в именах файлов не поддерживаются!)"

        Прямо так с восклицательным знаком и работает? В Баше?

        Чем дефолтный "|" в качестве сепаратора не устраивает? Гораздо более читабельно всё будет.

        IFS имеет смысл задавать для конкетной команды, чтобы он не загрязнял дефолт для остального скрипта.

        Не так:

         
        IFS='|'
        ...
        do_some $path

        А так:

         
        ...
        IFS='|' do_some $path

        > указывать разделитель как '\n'

        Так можно, но мудрить надо, навскидку не разберусь.

        • Как передать несколько путей с пробелами через переменную?, !*! inFlowiaLab, 09:26 , 30-Май-20 (8)
          > Прямо так с восклицательным знаком и работает? В Баше?

          Ну да. В первой строке скрипта: "#!/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"
          :

          • Как передать несколько путей с пробелами через переменную?, !*! Licha Morada, 19:04 , 30-Май-20 (9)
            >> Прямо так с восклицательным знаком и работает? В Баше?
            > Ну да. В первой строке скрипта: "#!/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 останется на дефолте, а во втором изменится.

            Работает в строчках которые запускают команды. В строчках которые присваивают значения, там странно.
            Разумеется, делайте как вам будет удобнее.

            • Как передать несколько путей с пробелами через переменную?, !*! DiabloPC, 21:24 , 30-Май-20 (10)
              А зачем внутри одного скрипта плодить несколько вариантов IFS (да и любых других переменных тоже)? После завершения работы скрипта оболочка всеравно вернётся к дефолту (ес-сно если не юзать export).
              • Как передать несколько путей с пробелами через переменную?, !*! Licha Morada, 04:11 , 01-Июн-20 (13)
                > А зачем внутри одного скрипта плодить несколько вариантов IFS (да и любых
                > других переменных тоже)? После завершения работы скрипта оболочка всеравно вернётся к
                > дефолту (ес-сно если не юзать export).

                Если скрипт небольшой, то пофигу.
                А если большой, то имеет смысл стараться держать окружение максимально близким к дефолту, держа под контролем все отклонения, и возвращаясь к дефолту при первой возможности. Дабы меньше дебажить странные глюки.
                Собственно, это не догма, а практическое соображение. Если оно облегчает жизнь, то надо следовать. Если нет, значит на то причина есть.

                • Как передать несколько путей с пробелами через переменную?, !*! DiabloPC, 08:23 , 01-Июн-20 (14)
                  >> А зачем внутри одного скрипта плодить несколько вариантов IFS (да и любых
                  >> других переменных тоже)? После завершения работы скрипта оболочка всеравно вернётся к
                  >> дефолту (ес-сно если не юзать export).
                  > Если скрипт небольшой, то пофигу.
                  > А если большой, то имеет смысл стараться держать окружение максимально близким к
                  > дефолту, держа под контролем все отклонения, и возвращаясь к дефолту при
                  > первой возможности. Дабы меньше дебажить странные глюки.
                  > Собственно, это не догма, а практическое соображение. Если оно облегчает жизнь, то
                  > надо следовать. Если нет, значит на то причина есть.

                  Имхо, в рамках одного скрипта проще такое делать для всего скрипта. Т.е установить переменную в самом начале и помнить что в этом скрипте IFS это перенос строки, а не пробел, а не сидеть потом через пару сотен строк и не офигевать почему оно вдруг работает не так как ожидалось, а оказывается мы где-то там забыли сбросить переменную до пробела. Оно вроди и мелочь, но на отладке такого потом глаза могут повылезать пока найдёшь %)

            • Как передать несколько путей с пробелами через переменную?, !*! inFlowiaLab, 13:54 , 01-Июн-20 (20)
              > Работает в строчках которые запускают команды. В строчках которые присваивают значения,
              > там странно.

              Из за этой странности не изменять IFS глобально не получится. Провёл много проб и понял, что пока не сделаешь так:


              IFS=$'\n' p='путь
              путь
              путь'
              IFS=$'\n' ls $p

              ничего не заработает. То есть в любом случае при задании переменной прихоидится указывать IFS=$'\n' а раз уж это всё равно глобально изменит IFS то смысла особого в этом нет.

              Очень всё это странно, потому что если менять IFS на весь скрипт и не парится, то можно задавать переменную с путём даже до изменения IFS.

              • Как передать несколько путей с пробелами через переменную?, !*! DiabloPC, 20:52 , 01-Июн-20 (21)
                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"

                Т.е если это писать руками всё норм, а если делать подстановку - весь выхлоп пишется как один элемент массива, хотя выхлоп подстановки соответствует тому что пишется руками и должен быть воспринят как несколько элементов. Но нет, оно его тащит как строку и хоть ты тресни %)
      • Как передать несколько путей с пробелами через переменную?, !*! DiabloPC, 21:27 , 30-Май-20 (11)
        > непонятного - это почему то не прокатывает указывать разделитель как '\n'

        IFS=$'\n'

        • Как передать несколько путей с пробелами через переменную?, !*! inFlowiaLab, 18:22 , 31-Май-20 (12)
          >> непонятного - это почему то не прокатывает указывать разделитель как '\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': Нет такого файла или каталога


          Господи, откуда он символ доллара в пути взял...
          • Как передать несколько путей с пробелами через переменную?, !*! DiabloPC, 08:30 , 01-Июн-20 (15) +1
            Потому что не 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


            • Как передать несколько путей с пробелами через переменную?, !*! inFlowiaLab, 12:26 , 01-Июн-20 (17)
              > Конструкция $'\n' это и есть перенос строки.

              Всё! Вот чего я не понимал в контексте проблемы с IFS='\n'. Что \n заменяется на перенос строки только в одинарных кавычках и только если перед ними стоит $

              • Как передать несколько путей с пробелами через переменную?, !*! DiabloPC, 12:37 , 01-Июн-20 (19)
                >> Конструкция $'\n' это и есть перенос строки.
                > Всё! Вот чего я не понимал в контексте проблемы с IFS='\n'. Что
                > \n заменяется на перенос строки только в одинарных кавычках и только
                > если перед ними стоит $

                Агась. А ниже вон человек самый правильный вариант для этого всего предложил. И IFS трогать ненужно и, по идее, там даже переносы строк не помеха.

                • Как передать несколько путей с пробелами через переменную?, !*! And, 21:14 , 01-Июн-20 (22)
                  > И 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/bash

                  mkdir "/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/bash

                  PS4="+:$( basename \"\${0}\" ):\${LINENO}: "
                  set -xeu -o pipefail

                  function 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_done

                  set +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, здесь ведь научился.

  • Как передать несколько путей с пробелами через переменную?, !*! And, 12:07 , 01-Июн-20 (16) +1

    #!/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"




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру