РЕШЕНО - субшелл (записать данные из субшелла в переменную)

На самом деле это единственный раздел про unix на этом форуме

Модератор: /dev/random

v4567
Сообщения: 138

РЕШЕНО - субшелл

Сообщение v4567 » 18.04.2019 02:22

Есть код:

Код: Выделить всё


i=0

find "/dir" -type f -print0 | while read -r -d '' per1
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done

printf "%s\n" "${dir[@]}"

printf не выведет ничего, так как read запускает свой субшелл и все данные останутся там, пробовал всякие перенаправления ничего не получается. Записывать в файл на диск нельзя. export не поможет так как он экспортирует для потомков, а не для родителей. Можно как то передать данные в переменную-массив в самом скрипте не прибегая к записи в файл на дик?

Если заменить на:

Код: Выделить всё


i=0

for per1 in $(find "/dir" -type f)
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done

printf "%s\n" "${dir[@]}"

То тогда если будут файлы с названиями "*, ? " и т.д. то почему то find заносит в переменную один и тот же файл несколько раз, просто команда find отрабатывает нормально, с for-ом получается какая то чепуха.

Вот такая конструкция:

Код: Выделить всё


i=0

for per1 in "/dir/*" "/dir/.*" -type f
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done

printf "%s\n" "${dir[@]}"

Работает просто превосходно, но совсем не просто сделать рекурсию по вложенным папкам.
Последний раз редактировалось v4567 20.04.2019 19:41, всего редактировалось 1 раз.
Спасибо сказали:

Аватара пользователя
Hephaestus
Сообщения: 2516
Статус: Многоуважаемый джинн...
ОС: Slackware64-14.1/14.2

Re: субшелл

Сообщение Hephaestus » 18.04.2019 09:58

v4567 писал:
18.04.2019 02:22
Можно как то передать данные в переменную-массив в самом скрипте не прибегая к записи в файл на дик?
Объявить массив dir глобально (до входа в цикл), например.
Вроде должно сработать, но точно не помню.
Пускай скрипят мои конечности.
Я - повелитель бесконечности...
Мой блог
Спасибо сказали:

Аватара пользователя
Vascom
Сообщения: 1471
ОС: Fedora 30

Re: субшелл

Сообщение Vascom » 18.04.2019 10:18

А в чём конкретно задача?
Нельзя ли вывод find записать в переменную, а из неё уже вытаскивать в цикле и записывать нужное в массив?
Спасибо сказали:

Аватара пользователя
Bizdelnick
Модератор
Сообщения: 15620
Статус: grammatikführer
ОС: Debian GNU/Linux

Re: субшелл

Сообщение Bizdelnick » 18.04.2019 11:03

v4567 писал:
18.04.2019 02:22
Можно как то передать данные в переменную-массив в самом скрипте не прибегая к записи в файл на дик?
Можно использовать подстановку процесса:

Код: Выделить всё

#!/bin/bash

readarray -d '' dir < <(find /dir -type f -print0)
printf "%s\n" "${dir[@]##*/}"
v4567 писал:
18.04.2019 02:22
если будут файлы с названиями "*, ? " и т.д. то почему то find заносит в переменную один и тот же файл несколько раз
find тут ни при чём, просто его вывод обрабатывается шеллом, который раскрывает такие имена как шаблоны glob.
Пишите правильно:
в консоли
вкупе (с чем-либо)
в общем
вообще
в течение (часа)
команда
новичок
нюанс
приемлемо
проблема
пробовать
трафик
Спасибо сказали:

Аватара пользователя
olecya
Сообщения: 32
ОС: debian, fedora (i3-wm)

Re: субшелл

Сообщение olecya » 18.04.2019 11:05

Код: Выделить всё

dir=($(find "/dir" -type f -printf "%f "))
echo ${dir[@]}
Добавлено (11:15):

Код: Выделить всё

xargs -n1 <<<${dir[@]}

Код: Выделить всё

fmt -1 <<<${dir[@]}

Код: Выделить всё

grep -o "[^ ]\+" <<<${dir[@]}
Спасибо сказали:

Аватара пользователя
ormorph
Сообщения: 878
ОС: Gentoo

Re: субшелл

Сообщение ormorph » 18.04.2019 11:22

v4567 писал:
18.04.2019 02:22
printf не выведет ничего, так как read запускает свой субшелл и все данные останутся там, пробовал всякие перенаправления ничего не получается.
Хм, при чем тут субшелл?
В данном случае конвейер не пропускает переменную. Нужно менять конструкцию без использования конвейера.
Для while можно так:

Код: Выделить всё

i=0
while read -r -d '' per1
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done <<< "$(find "/dir" -type f -print0)"

printf "%s\n" "${dir[@]}"
Работу скрипта даже не проверял, но работать будет...
Спасибо сказали:

Аватара пользователя
olecya
Сообщения: 32
ОС: debian, fedora (i3-wm)

Re: субшелл

Сообщение olecya » 18.04.2019 11:26

Код: Выделить всё

mapfile dir < <(find $(cd /dir) -type f -print0 | xargs -0)
Добавлено (11:42):

Код: Выделить всё

mapfile -t dir < <(find "/dir" -type f -print0 | xargs -0 -I{} basename {})
Добавлено (11:51):
olecya писала:
18.04.2019 11:26
mapfile dir < <(find $(cd /dir) -type f -print0 | xargs -0)
Да, вот тут я погорячилась )))

Код: Выделить всё

mapfile dir < <(cd "/dir" && find -type f -print0 | xargs -0)
Спасибо сказали:

Аватара пользователя
Bizdelnick
Модератор
Сообщения: 15620
Статус: grammatikführer
ОС: Debian GNU/Linux

Re: субшелл

Сообщение Bizdelnick » 18.04.2019 12:07

ormorph писал(а):
18.04.2019 11:22
Хм, при чем тут субшелл?
В данном случае конвейер не пропускает переменную.
Сабшелл здесь при том, что конвейер создаёт сабшелл, и именно поэтому определённая в нём переменная недоступна из основного шелла.
Последний раз редактировалось Bizdelnick 18.04.2019 12:10, всего редактировалось 1 раз.
Пишите правильно:
в консоли
вкупе (с чем-либо)
в общем
вообще
в течение (часа)
команда
новичок
нюанс
приемлемо
проблема
пробовать
трафик
Спасибо сказали:

Аватара пользователя
olecya
Сообщения: 32
ОС: debian, fedora (i3-wm)

Re: субшелл

Сообщение olecya » 18.04.2019 12:09

А может все гораздо проще и нет необходимости в нулевом разделителе и рекурсивном поиске?

Код: Выделить всё

dir=($(cd /dir; ls -p | grep -v '/'))
Спасибо сказали:

Аватара пользователя
ormorph
Сообщения: 878
ОС: Gentoo

Re: субшелл

Сообщение ormorph » 18.04.2019 13:29

Bizdelnick писал:
18.04.2019 12:07
Сабшелл здесь при том, что конвейер создаёт сабшелл, и именно поэтому определённая в нём переменная недоступна из основного шелла.
Есть такое, только read тут не причем, это внутренняя команда bash.
Спасибо сказали:

Аватара пользователя
Bizdelnick
Модератор
Сообщения: 15620
Статус: grammatikführer
ОС: Debian GNU/Linux

Re: субшелл

Сообщение Bizdelnick » 18.04.2019 14:05

olecya писала:
18.04.2019 12:09
А может все гораздо проще и нет необходимости в нулевом разделителе и рекурсивном поиске?

Код: Выделить всё

dir=($(cd /dir; ls -p | grep -v '/'))
Вот на ls точно полагаться не надо. Не только в этом случае, а вообще никогда.
Последний раз редактировалось Bizdelnick 18.04.2019 16:53, всего редактировалось 1 раз.
Пишите правильно:
в консоли
вкупе (с чем-либо)
в общем
вообще
в течение (часа)
команда
новичок
нюанс
приемлемо
проблема
пробовать
трафик
Спасибо сказали:

v4567
Сообщения: 138

Re: субшелл

Сообщение v4567 » 18.04.2019 16:11

Bizdelnick писал:
18.04.2019 11:03
find тут ни при чём, просто его вывод обрабатывается шеллом, который раскрывает такие имена как шаблоны glob.
Да, так и есть.

Если взять в директории "/dir" создать два файла "11" и "*", а потом выполнить в баше команду:

Код: Выделить всё

find "/dir" -type f
то выведет:

Код: Выделить всё

/dir/11
/dir/*
А вот если выполнить такую команду:

Код: Выделить всё

i=0 ; for per in $(find "/dir" -type f) ; do (( i++ )) ; dir1[$i]="$per" ; done ; printf "%s\n" "${dir1[@]}"
Вот она в читаемом виде:

Код: Выделить всё

i=0

for per in $(find "/dir" -type f)
 do
    (( i++ ))
    dir1[$i]="$per"
done

printf "%s\n" "${dir1[@]}" 
То выведет:

Код: Выделить всё

/dir/11
/dir/11
/dir/*
/dir/11

Файл "11" выведен два лишних раза.
Скорее всего из-за $(.....)

Соответственно если файлов будет много, работа скрипта затянется очень на долго.
Попробовал вывести файлы их было очень много и вроде не было файлов с названием "*", но скрипт так и не завершился.
Пришлось его убивать по ctrl+C
Спасибо сказали:

v4567
Сообщения: 138

Re: субшелл

Сообщение v4567 » 18.04.2019 16:34

ormorph писал(а):
18.04.2019 11:22

Для while можно так:

Код: Выделить всё

i=0
while read -r -d '' per1
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done <<< "$(find "/dir" -type f -print0)"

printf "%s\n" "${dir[@]}"
Работу скрипта даже не проверял, но работать будет...
Не подходит, вот что выдаёт:

Код: Выделить всё

строка 41: предупреждение: command substitution: ignored null byte in input
В моём скрипте, строка 41 это:

Код: Выделить всё

done <<< "$(find "/dir" -type f -print0)"
Добавлено (16:34):
Кстати, что означают эти три символа "<<<"?
Добавлено (16:37):
Без -print0, уже не ругается, но и ничего не вывелось, массив пустой.
Добавлено (16:44):
Замечательно отработал вот такой вариант:

Код: Выделить всё

i=0
while read -r -d '' per1
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done < <(find "/dir" -type f -print0)

printf "%s\n" "${dir[@]}"
Добавлено (16:46):
Получается различие в <<< $() < <()

Я особо с файлами не работал, но как оказалось, работать с файлами, папками в bash скриптах, что бы учесть все нюансы, это очень сложная задача.
Спасибо сказали:

Аватара пользователя
ormorph
Сообщения: 878
ОС: Gentoo

Re: субшелл

Сообщение ormorph » 18.04.2019 17:19

v4567 писал:
18.04.2019 16:34
Кстати, что означают эти три символа "<<<"?
Перенаправляет вывод команды в цикл while.
v4567 писал:
18.04.2019 16:34
Без -print0, уже не ругается, но и ничего не вывелось, массив пустой.
Странно бы было, у вас стоит параметр -d '', он читает в качестве разделителя "ничто", а не новую строку, что не айс, в потоке еще сработает. Надо бы убрать данный ключик:

Код: Выделить всё

i=0
while read -r per1
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done <<< "$(find "/dir" -type f)"

printf "%s\n" "${dir[@]}
Такой вариант должен сработать.
v4567 писал:
18.04.2019 16:34
Получается различие в <<< $() < <()
Различие есть, первый вариант сначала записывает вывод в буфер, а потом направляет вывод в цикл, тут в качестве вывода выступает текст в кавычках.
Второй вариант направляет непосредственно поток в цикл, это позволяет обрабатывать вывод в реальном времени.
Последний раз редактировалось ormorph 18.04.2019 17:38, всего редактировалось 1 раз.
Спасибо сказали:

Аватара пользователя
/dev/random
Администратор
Сообщения: 4843
ОС: Gentoo

Re: субшелл

Сообщение /dev/random » 18.04.2019 17:33

ormorph писал(а):
18.04.2019 17:19
Странно бы было, у вас стоит параметр -d '', он читает в качестве разделителя "ничто", а не новую строку, что не айс, в потоке еще сработает. Надо бы убрать данный ключик:
-d '' означает использовать в качестве разделителя символ \0, единственный символ, запрещённый в полных путях к файлам. В вашем варианте могут встретиться файлы с переводом строки в именах, и этот перевод может быть воспринят как разделитель. В done <<< "$(find "/dir" -type f -print0)" проблема была в том, что шелл не умеет работать с \0 внутри строк, и удаляет его. done < <(find "/dir" -type f -print0) - корректный вариант, передающий вывод от find напрямую через пайп в read, не пропуская его через те места, где шелл может удалить \0.
Спасибо сказали:

v4567
Сообщения: 138

Re: субшелл

Сообщение v4567 » 18.04.2019 18:14

Код: Выделить всё

i=0
while read -r per1
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done <<< "$(find "/dir" -type f)"

printf "%s\n" "${dir[@]}
Ругается на строку:

Код: Выделить всё

while read -r '' per1

Код: Выделить всё

read: «»: это недопустимый идентификатор
Вот этот вариант работает отлично:

Код: Выделить всё

i=0
while read -r -d '' per1
do
   per2="${per1##*/}"
   (( i++ ))
   dir[$i]="$per2"
done < <(find "/dir" -type f -print0)

printf "%s\n" "${dir[@]}"
Тут новая проблема, я уже честно готов сдаться.

Мне надо найти одинаковые по имени файлы в двух директориях. Есть вложенные директории, заранее сколько вложенных директорий, сколько вообще директорий, файлов не известно.

Так вот я все названия загоняю в два массива, а потом сравниваю как строки. Алгоритм сравнения с убиранием повторяющихся названий файлов (во вложенных директориях могут быть файлы с одинаковыми именами, в выводе их не должно быть)и т.д готов. Уже (не без помощи вашего и других форумов) решил вопрос с нестандартными именами файлов и папок. На небольшом количестве файлов и папок работает всё прекрасно. Но когда файлов и папок очень много, при этом команда find "/dir" -type f выводит названия всех файлов за секунду полтары, то вышеприведённая конструкция, просто загнать все названия файлов в массив, затрачивает на это час. Это при том, что дело ещё не дошло до сравнения построчно.

Получается, что данная конструкция не приемлема, надо делать как то по другому. А как? Может такое вообще не рационально реализовывать в bash скрипте?

Подскажите если у кого есть какие нибудь мысли.
Скорее всего такое можно наверное реализовать на Си...
Спасибо сказали:

Аватара пользователя
Bizdelnick
Модератор
Сообщения: 15620
Статус: grammatikführer
ОС: Debian GNU/Linux

Re: субшелл

Сообщение Bizdelnick » 18.04.2019 18:26

v4567 писал:
18.04.2019 18:14
вышеприведённая конструкция, просто загнать все названия файлов в массив, затрачивает на это час.
А зачем Вы загоняете имена файлов в массив такой жуткой конструкцией? Используйте readarray/mapfile, примеры см. выше.
Пишите правильно:
в консоли
вкупе (с чем-либо)
в общем
вообще
в течение (часа)
команда
новичок
нюанс
приемлемо
проблема
пробовать
трафик
Спасибо сказали:

Аватара пользователя
/dev/random
Администратор
Сообщения: 4843
ОС: Gentoo

Re: субшелл

Сообщение /dev/random » 18.04.2019 18:34

Shell

readarray -d '' dup < <( (find "$dir1" -type f -printf '%f\0' | sort -zu; find "$dir2" -type f -printf '%f\0' | sort -zu) | sort -z | uniq -dz )
Заполнит массив dup именами файлов, имеющихся и в "$dir1" (или подкаталогах), и в "$dir2" (или подкаталогах). Имена подкаталогов включать в сравнение и вывод не будет. Если нужно, чтобы включал, то:

Shell

readarray -d '' dup < <( (find "$dir1" -type f -printf '%P\0' | sort -zu; find "$dir2" -type f -printf '%P\0' | sort -zu) | sort -z | uniq -dz )
Спасибо сказали:

v4567
Сообщения: 138

Re: субшелл

Сообщение v4567 » 18.04.2019 22:09

Пятый день мучаюсь над поставленной задачей, родил около 40 строк кода. Тут пришёл /dev/random и за одну секунду, одной строчкой кода, решил все проблемы.
Снимаю шляпу перед /dev/random!

/dev/random подскажите что почитать по bash скриптингу на русском языке.
http://rus-linux.net/MyLDP/BOOKS/abs-guide/flat/abs-book.html - это самое нормально, что попадалось, но как то там не договаривают что ли...... Прочтёшь и как то всё равно не понятно.

Вот из приведённой строчки кода вроде все команды знакомы, но вот так их все связать я не догадался.

Кстати Вы прошлый раз не написали название массива, а сейчас написали название массива но убрали команду diff. Я проверил, без diff работает, то есть сравнивает и одинаковые имена записывает без повторений. Вот от тут я что то вообще не пойму как оно без diff работает???
Спасибо сказали:

Аватара пользователя
/dev/random
Администратор
Сообщения: 4843
ОС: Gentoo

Re: субшелл

Сообщение /dev/random » 18.04.2019 22:31

v4567 писал:
18.04.2019 22:09
/dev/random подскажите что почитать по bash скриптингу на русском языке.
К сожалению, не знаю. На русском языке давно ничего не читаю, а bash изучал настолько давно, что уже и не помню, как именно.
v4567 писал:
18.04.2019 22:09
Кстати Вы прошлый раз не написали название массива, а сейчас написали название массива но убрали команду diff. Я проверил, без diff работает, то есть сравнивает и одинаковые имена записывает без повторений. Вот от тут я что то вообще не пойму как оно без diff работает???
Это была не команда. Это было название массива, я его переименовал из diff в dup, чтобы вы не путались.
v4567 писал:
18.04.2019 22:09
Вот из приведённой строчки кода вроде все команды знакомы, но вот так их все связать я не догадался.
find путь -type f -printf '%f\0' - найти файлы, вывести только их имена без путей, завершив каждое символом \0. Если использовать %P вместо %f, это будет означать вывод имён файлов с путями относительно каталога, в котором идёт поиск.

-z - во всех использованных командах, кроме find и readarray, означает использование \0 в качестве разделителя. Я уберу эту опциию из описания команд ниже, для краткости.

find ... | sort -u - отсортировать найденные имена и удалить дубликаты из списка.

( find ... | sort -u; find ... | sort -u ) - вывести 2 отсортированных списка, полученные из разных каталогов. Поскольку из каждого списка отдельно удалены дубликаты, то если что-то упоминается 2 раза, значит, находится в обоих списках.

( ... ) | sort - отсортировать этот двойной список, не удаляя дубликатов. Теперь дубликаты будут идти подряд.

... | uniq -d - вывести только идущие подряд дубликаты, каждый в единственном экземпляре.

readarray -d '' dup < <(...) - и прочитать эти дубликаты в массив dup. -d '' означает использование \0 как разделителя.
Спасибо сказали:

v4567
Сообщения: 138

Re: субшелл

Сообщение v4567 » 18.04.2019 22:41

Понятно, diff не нужен, uniq -d выведет одинаковые имена файлов.
Спасибо сказали:

v4567
Сообщения: 138

Re: субшелл

Сообщение v4567 » 20.04.2019 02:06

Как отметить, что эта тема решённая?
Спасибо сказали:

Аватара пользователя
/dev/random
Администратор
Сообщения: 4843
ОС: Gentoo

Re: субшелл

Сообщение /dev/random » 20.04.2019 08:23

v4567 писал:
20.04.2019 02:06
Как отметить, что эта тема решённая?
Отредактируйте в первом сообщении заголовок.
Спасибо сказали:

Аватара пользователя
olecya
Сообщения: 32
ОС: debian, fedora (i3-wm)

Re: субшелл

Сообщение olecya » 11.05.2019 15:53

Bizdelnick писал:
18.04.2019 14:05
olecya писала:
18.04.2019 12:09
А может все гораздо проще и нет необходимости в нулевом разделителе и рекурсивном поиске?

Код: Выделить всё

dir=($(cd /dir; ls -p | grep -v '/'))
Вот на ls точно полагаться не надо. Не только в этом случае, а вообще никогда.

Код: Выделить всё

dir=($(cd dir/ && compgen -d))
Спасибо сказали:

Аватара пользователя
Bizdelnick
Модератор
Сообщения: 15620
Статус: grammatikführer
ОС: Debian GNU/Linux

Re: субшелл

Сообщение Bizdelnick » 11.05.2019 16:42

olecya писала:
11.05.2019 15:53

Код: Выделить всё

dir=($(cd dir/ && compgen -d))
Не лучше. При работе с файлами надо всегда думать "а что, если в имени встретится символ…" (дальше список, в котором на первом месте идёт пробел, на втором — перевод строки, а тут уже в данном случае можно остановиться).
Пишите правильно:
в консоли
вкупе (с чем-либо)
в общем
вообще
в течение (часа)
команда
новичок
нюанс
приемлемо
проблема
пробовать
трафик
Спасибо сказали: