LINUX.ORG.RU

Переменные в многоуровневых списках

 , ,


0

1

Есть список из каталогов, при переходе в дочерний уровень списка, должна быть доступна переменная на родительский уровень списка. Независимо от глубины перехода в дочерние списки, должен сохранятся доступ к смежным родительским спискам по мере абстракции от дочерних уровней.

Уже сломал мозг над этой задачей. Получается только конструкция из вложенных друг в друга циклов но она имеет ограничения по количеству уровней и ужасный внешний вид + большой размер.

Далее посмотрел в сторону функций, которые должны назначать переменные по мере прохождения уровней, но тут моих знаний совсем не хватает. Есть идеи?

Какой язык программирования? (Если речь идёт о программировании, конечно, но мне кажется, что идёт.)

Далее посмотрел в сторону функций, которые должны назначать переменные по мере прохождения уровней, но тут моих знаний совсем не хватает. Есть идеи?

Именно так и нужно делать. Древовидные структуры данных часто обходят при помощи рекурсии (это не обязательно так делать - рекурсивный код обычно возможно переписать так, чтобы не использовать рекурсию - но так гораздо удобнее). Функция должна вызвать саму себя для каждого дочернего каталога, чтобы его обойти, а также передать в качестве аргументов родительский уровень списка и любое другое необходимое состояние. Не забудьте условие останова: рано или поздно очередной вызов Вашей функции должен завершиться, не вызывая сам себя.

По крайней мере, если в Вашем языке есть локальные переменные.

AITap ★★★★★
()
Ответ на: комментарий от AITap

Какой язык программирования?

Форум вроде как linux.org, а не cisharp.omg) Только bash, only hardcore

Функция должна вызвать саму себя для каждого дочернего каталога

Ну это очевидно..

а также передать в качестве аргументов родительский уровень списка

В этом и проблема. Я не знаю как это сделать) Функция должна прибавлять по одной переменной по мере вхождения в дочерние уровни, значения переменных, являются пути к спискам... Осталось только заставить считать уровни и присваивать именам переменных по +1 к уровню. Так сойдёт?

vachicul
() автор топика
Ответ на: комментарий от vachicul

Ты тут тупаря не включай. На шарпея ты точно не тянешь, а то пошлём на F#.

Обход дерева описан везде на любых ЯП. Если мог сюда написать, то сможешь и нагуглить. Я тебя уже ткнул, чтобы ты мозг свой не напрягал, харэ.

Oldboy
()
Ответ на: комментарий от vachicul

Форум вроде как linux.org, а не cisharp.omg) Только bash, only hardcore

На GNU/Linux работает очень много разных языков программирования. bash имеет свои преимущества, но для многих задач неудобен. Какова Ваша задача? Может быть, для неё есть более подходящий инструмент?

Именно bash или POSIX shell? Если bash, возможно, Вам помогут примеры из ABSG?

Функция должна прибавлять по одной переменной по мере вхождения в дочерние уровни, значения переменных, являются пути к спискам…

Не понял, Вам нужна функция, которая принимает переменное количество аргументов? По идее, с "$@" можно работать как с bash-массивом.

Пожалуйста, поясните, выполнение какой именно операции вызывает проблемы. Назовите типы всех переменных, которые в ней участвуют; приведите примеры исходных значений и желаемое конечное значение.

AITap ★★★★★
()
Ответ на: комментарий от AITap

Примеров много и все они неудачные, мне проще на пальцах объяснить

1 Выводится список записей и категорий потомков

entry
entry2
path/
path2/

2 При выборе категории, программа выводит список внутри этой категории, там могут быть записи и другие категории потомки. Выводится только название категорий и записей, без их полного пути

3 При выборе записи, программа её обрабатывает и выводит текущий список в котором была выбрана эта запись

4 Программе нужно знать как подняться в смежную родительскую категорию из текущего расположения, независимо от того, на сколько глубоко она спустилась в потомков.

Пример:

entry
path/

вошли в path/ вывела список потомка

entry2
path2/

вошли в path2/ вывела список потомка

entry3
path3/

вошли в path3/ вывела список потомка

entry4
entry10

Выбрала entry10, обработала и вернула такой же список Теперь программе нужно вернуться в path2/

entry3
path3/

Затем нужно вернуться в path/

entry
path/

В переменную нужно как-то записывать весь путь, по мере вхождения в потомков

$var
path/path2/path3/

А при выходе из потомка, нужно что бы переменная уже ссылалась на все последующие родительские пути

$var
path/path2/

$var
path/
vachicul
() автор топика
Последнее исправление: vachicul (всего исправлений: 1)
Ответ на: комментарий от vachicul
entry
entry2
path/
path2/

Это передают как аргументы функции? Как поток строк на стандартный вход? Как bash-массив при помощи declare -a? Как-то ещё? Что из этого - записи, а что - категории?

В переменную нужно как-то записывать весь путь, по мере вхождения в потомков

Вот пример, как рекурсивная функция может накапливать состояние по мере прохода вглубь дерева и возвращать его:

f () {
 local current="$1" child="$2" ret
 for child_of_child in $(get_children_of_child "$child"); do
  ret="${ret} $(f "${current}/${child}" "$child_of_child")"
 done
 echo "$ret"
}

Было бы неплохо, если бы Вы ответили и на другие мои вопросы в предыдущем сообщении.

AITap ★★★★★
()
Ответ на: комментарий от AITap

Именно bash или POSIX shell? Если bash, возможно, Вам помогут примеры из ABSG?

именно bash

Не понял, Вам нужна функция, которая принимает переменное количество аргументов? По идее, с «$@» можно работать как с bash-массивом.

По идее) не совсем понимаю что значит «переменное количество аргументов». Важно запомнить всех родителей и обращаться к ним по мере подъема наверх.

Это передают как аргументы функции? Как поток строк на стандартный вход? Как bash-массив при помощи declare -a? Как-то ещё? Что из этого - записи, а что - категории?

Что значит «передают аргументы функции»? Это просто список, можете для примера его вывести способом ls. Записи без слэша, а категории со слэшем в конце

Вот пример, как рекурсивная функция может накапливать состояние по мере прохода вглубь дерева и возвращать его:

С этим нужно ещё разобраться. Ничего сложнее не видел

Сейчас попробую подробнее визуализировать

vachicul
() автор топика
Ответ на: комментарий от AITap
searchpath() {
list=$(ls -l /parent/"$unit") #Программа выводит список в каталоге /parent/ Для $unit ещё нет значения, оно появится на втором проходе этой функции
echo "$list"
entry
path/
read var #здесь пользователь вводит номер строки для выбора. Предположим выбор 2 - path/
if [ "$var" = "0" ]; then depth=$(( $depth - 1 )); searchpath; fi #Если выбор 0, программа должна уменьшить глубину (как вариант), затем заново запустить функцию 
choose=$(echo "$list" | sed -n "$var"p) #переменная принимает значение - имя строки
if ls -l /parent/"$unit""$choose" | grep "/"; then #проверяет является ли строка каталогом
depth=$(( $depth + 1 )) #если является значит глубина увеличена на 1 (как вариант)
(здесь должно быть что-то, что создаёт переменную $path1 со значением path/
unit=$(echo "$unit""$choose") #Объединяет предыдущее значения пути с новой строкой. Для первого прохода будет path/ для второго прохода $unit может быть path/path2/, то есть старый $unit и новый $choose и тд.
searchpath #Повторить проход
else #если строка оказывается записью
rm /parent/"$unit""$choose" #удаляет её
searchpath # и запускает новый проход, в том же каталоге, где была найдена запись
fi
}

В строке if [ «$var» = «0» ]; then должно быть что-то ещё, например $unit=$unit минус предыдущий $choose, то есть вернуть его в состояние до предыдущего прохода. Но как быть с родителем который ещё старше? Для этого нужно как-то обращаться к $path1

Так понятно? Вы просите примеры того, что я не могу сделать..

vachicul
() автор топика
Ответ на: комментарий от AITap

Выводится список для выбора

entry

path/

Выбираем вторую строку - path/ Программа выводит содержимое path/

entry2

path2/

path3/

Входим дальше, выбираем path3/ Выводим содержимое path3/

path4/

path5/

path6/

Выбираем 3, входим в каталог path6/ Выводим содержимое path6/

path7/

path8/

path9/

entry10

Теперь выбираем 4 - entry10 - это запись. Обрабатываем её и выводим тот же список path6/

path7/

path8/

path9/

entry10

Тепрь выбираем 0 - то есть вернуться в родителя path3/, для этого нужно что бы программа знала полный путь path/path3/ и вывела список из него

path4/

path5/

path6/

Ещё раз выбираем 0 и возвращаемся в /path

entry2

path2/

path3/

Ещё раз жмём 0 и возвращаемся в корень /

entry

path/

как это реализовано в моих циклах с ограниченной глубиной

list=$(ls -l /parent/)
while true; do
	echo "$list"
	read str
	if [ $str = 0 ]; then break; fi
	choose=$(echo "$list" | sed -n "$str"p)
	check=$(echo $choose | grep "/")
	while true; do
	if [ -n "$check" ]; then
		list2=$(ls -l /parent/$choose)
		echo "$list2"
		read str
		if [ $str = 0 ]; then break; fi
		choose2=$(echo "$list2" | sed -n "$str"p)
		check=$(echo $choose2 | grep "/")
		while true; do
		if [ -n "$check" ]; then
			list3=$(ls -l /parent/$choose$choose2)
			echo "$list3"
			read str
			if [ $str = 0 ]; then break; fi
			choose3=$(echo "$list3" | sed -n "$str"p)
			check=$(echo $choose3 | grep "/")
			while true; do
			if [ -n "$check" ]; then
				list4=$(ls -l /parent/$choose$choose2$choose3)
				echo "$list4"
				read str
				if [ $str = 0 ]; then break; fi
				choose4=$(echo "$list4" | sed -n "$str"p)
				rm /parent/$choose$choose2$choose3$choose4
			else
				rm /parent/$choose$choose2$choose3
			fi; done
		else
			rm /parent/$choose$choose2
		fi; done
	else
		rm /parent/$choose
	fi; done
done
Такая лапша меня не устраивает и встраивать туда дополнительный код это боль, а количество уровней ограничено длинной лапши

vachicul
() автор топика
Последнее исправление: vachicul (всего исправлений: 3)
Ответ на: комментарий от vachicul

именно bash

Тогда обязательно изучите ABSG или его русский перевод.

не совсем понимаю что значит «переменное количество аргументов»

Функциям можно передавать аргументы, как другим программам:

f() { echo "My arguments: $*"; }
f 1 2 3 "4 5 6"

Они сохраняются в переменных $1, $2, …, а их число - в $#. Получить их в виде списка, подходящего для передачу в другую функцию или команду можно при помощи "$@" (именно так, с кавычками). Подробнее смотрите в ABSG или документации к bash.

Это просто список, можете для примера его вывести способом ls. Записи без слэша, а категории со слэшем в конце

Вопрос в том, как именно этот список получает программа. Из примера ниже не понимаю, где хранится сам список. Вы имеете в виду, что это просто файлы в определённой директории?

AITap ★★★★★
()
Ответ на: комментарий от vachicul

Ваша функция searchpath() не принимает аргументов. Задачу наверняка можно решить проще, если передавать состояние через них, не полагаясь на глобальные переменные, которые сохраняют свои значения от вызова к вызову.

Возьмите любой учебник по программированию, например, Столяров, 2021, т.1 §2.3.7 и §2.11 и изучите, как обычно устроены рекурсивные программы. Суть в том, чтобы облегчить себе задачу, складывая состояние в локальные переменные на стеке. Функция searchpath может вызывать себя несколько раз, и в каждом таком вызове будет своя переменная unit, depth, choose со своим значением. Чтобы вернуться на предыдущий шаг, достаточно сделать return, и локальные переменные сами примут свои значения на момент предыдущего вызова функцией самой себя.

list=$(ls -l /parent/"$unit")

Это довольно хрупко. declare -a list=(/parent/"$unit"/*) - и теперь в массиве ${list[@]} хранятся отдельные записи из Вашей директории.

choose=$(echo "$list" | sed -n "$var"p)

К элементам массива можно обращаться как "${list[$var]}".

if ls -l /parent/"$unit""$choose" | grep "/"; then #проверяет является ли строка каталогом

Изучите help test. Проверка на каталог - это test -d "$path_to_whatever".

if [ "$var" = "0" ]; then depth=$(( $depth - 1 )); searchpath; fi #Если выбор 0, программа должна уменьшить глубину (как вариант), затем заново запустить функцию

В строке if [ «$var» = «0» ]; then должно быть что-то ещё, например $unit=$unit минус предыдущий $choose, то есть вернуть его в состояние до предыдущего прохода. Но как быть с родителем который ещё старше? Для этого нужно как-то обращаться к $path1

Если Вы будете использовать аргументы функций и локальные переменные, Вам не придётся вручную возвращать значения глобальных переменных к предыдущему значению перед выходом из функции searchpath (там должен быть return, а не searchpath, иначе функция будет бесконечно вызывать саму себя и никогда не завершится). См. также bashdb - отладчик для bash-скриптов с пошаговым проходом и другими удобствами.

Как быть с родителями и их родителями? Вызывая саму себя, searchpath может передавать себе массив родителей плюс текущий unit:

# вызов: searchpath "$foo" "$bar" "${parents[@]}"
searchpath() {
 local foo bar
 foo="$1"; shift
 bar="$2"; shift
 declare -a parents=("$@")
 # ...
 # настало время вызвать саму себя, добавив к массиву parents ещё одно значение на время этого вызова
 f "$foo" "$bar" "$current_unit" "${parents[@]}" # $current_unit станет первым элементом $parents[@] внутри этого вызова
 # ...
}

Тогда первый родитель получается "${parents[0]}", второй - "${parents[1]}", а их количество - ${#parents}.

rm /parent/"$unit""$choose" #удаляет её
searchpath # и запускает новый проход, в том же каталоге, где была найдена запись

А не получится ли выполнить работу этой функции при помощи find с некоторыми условиями и флагом -delete для удаления найденных файлов? Или Вам нужна интерактивность?

AITap ★★★★★
()
Последнее исправление: AITap (всего исправлений: 2)
Ответ на: комментарий от AITap

Вопрос в том, как именно этот список получает программа. Из примера ниже не понимаю, где хранится сам список. Вы имеете в виду, что это просто файлы в определённой директории?

Это не файлы и не директории. Программа выводит список из собственной базы данных, эта программа не имеет нормального api, только примитивный способ вывести содержание каталогов в таком вот виде.

Они сохраняются в переменных $1, $2, …, а их число - в $#. Получить их в виде списка, подходящего для передачу в другую функцию или команду можно при помощи «$@» (именно так, с кавычками). Подробнее смотрите в ABSG или документации к bash.

Очень интересно, сейчас попробую применить

vachicul
() автор топика
Ответ на: комментарий от AITap

Чтобы вернуться на предыдущий шаг, достаточно сделать return, и локальные переменные сами примут свои значения на момент предыдущего вызова функцией самой себя.

return это подобие continue/break в циклах? Я думал функция статична и при повторном вызове она просто читается заново с новыми переменными ничего не запоминая из прошлого.

Это довольно хрупко. declare -a list=(/parent/«$unit»/*) - и теперь в массиве ${list[@]} хранятся отдельные записи из Вашей директории. choose=$(echo «$list» | sed -n "$var"p) К элементам массива можно обращаться как «${list[$var]}».

Почему хрупко? Может рассыпаться?) Хорошая наводка, попробую использовать, но пока смутно всё видится, по запчастям

Изучите help test. Проверка на каталог - это test -d «$path_to_whatever».

Уже ознакомился. Она проверят только название на наличие / в конце? Потому что у меня не файлы и каталоги, а простые строки из базы данных

Если Вы будете использовать аргументы функций и локальные переменные, Вам не придётся вручную возвращать значения глобальных переменных к предыдущему значению перед выходом из функции searchpath (там должен быть return, а не searchpath, иначе функция будет бесконечно вызывать саму себя и никогда не завершится).

Локальные переменные те, которые объявлены внутри функции?

См. также bashdb - отладчик для bash-скриптов с пошаговым проходом и другими удобствами.

С английским засада. Не профессионально но и я любитель

Тогда первый родитель получается «${parents[0]}», второй - «${parents[1]}», а их количество - ${#parents}.

Звучит красиво но пример кода не ясен. Что значит «local foo bar» в начале функции?

А не получится ли выполнить работу этой функции при помощи find с некоторыми условиями и флагом -delete для удаления найденных файлов? Или Вам нужна интерактивность?

Это не файлы, у задачи жёсткая привязка к программе с её базой данных, нужно сохранять интерактивность до полного выхода из родителей и корня. Если проверка показывает что это запись, а не каталог, нужно лишь передать этот аргумент на выполнение другой команде, затем вернуть тот же список, в котором была выбрана запись, для дальнейшей миграции по родителям и потомкам

vachicul
() автор топика
Ответ на: комментарий от vachicul

return это подобие continue/break в циклах?

Это возврат из функции, с возможностью выставить $?.

Я думал функция статична и при повторном вызове она просто читается заново с новыми переменными ничего не запоминая из прошлого.

Глобальные переменные сохраняются (иначе Вам не пришлось бы делать «$unit=$unit минус предыдущий $choose» перед возвратом из функции), если только функция не исполняется в дочернем процессе (что легко случайно устроить). Локальные существуют в виде нескольких экземпляров, по одному на каждый вызов функции.

Почему хрупко? Может рассыпаться?

Может сломаться, если в именах файлов будут специальные символы. Например, в POSIX ничего не запрещает создать файл с именем из перевода на новую строку или escape-последовательностью для очистки терминала. Что в таком случае сделает ls? А другая, более новая или более старая её версия?

Она проверят только название на наличие / в конце?

Нет, делает системный вызов stat и смотрит на возвращённую ядром информацию.

Локальные переменные те, которые объявлены внутри функции?

Которые объявлены внутри функции при помощи local или declare без -g.

Звучит красиво но пример кода не ясен. Что значит «local foo bar» в начале функции?

local объявляет переменные локальными. Локальные переменные живут внутри вызова функции, а не в глобальном пространстве имён. См. help local и соответствующую часть ABSG. Функция searchpath может вызвать саму себя, выставить переменные $foo и $bar, вернуться обратно, и значения как бы восстановятся на старые, которые там были до внутреннего вызова searchpath. Во многих случаях это удобнее, чем вручную выставлять старое значение переменной перед возвратом из рекурсивного вызова.

В результате получается, что функция принимает два аргумента или больше, первые два сохраняет в именованных локальных переменных $foo и $bar (кстати, у меня ошибка, связанная с нумерацией аргументов после операции shift), а все остальные - в массив "${parents[@]}", где их может быть 0 или натуральное число. Внутренние вызовы наращивают массив parents, сохраняя доступ ко всем родителям родителей, но с возвратом он автоматически становится короче.

AITap ★★★★★
()
Ответ на: комментарий от AITap

Это возврат из функции, с возможностью выставить $?

$? - это и есть возврат переменной в предыдущее состояние?

Нет, делает системный вызов stat и смотрит на возвращённую ядром информацию.

Не подходит, потому что программа просто выводит список

Что в таком случае сделает ls? А другая, более новая или более старая её версия?

Всё очень просто, программа выводит список в указанном каталоге (из своей базы данных):

$ program ls /parent/path
entry2
path2/
path3/
$ program ls /parent/path/path3
path4/
path5/
path6/
$ 

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

Если это возможно, тогда не «во многих», а во всех случаях. Зачем писать лишний код если можно его не писать?

В результате получается, что функция принимает два аргумента или больше, первые два сохраняет в именованных локальных переменных $foo и $bar (кстати, у меня ошибка, связанная с нумерацией аргументов после операции shift), а все остальные - в массив «${parents[@]}», где их может быть 0 или натуральное число. Внутренние вызовы наращивают массив parents, сохраняя доступ ко всем родителям родителей, но с возвратом он автоматически становится короче.

Полезно, понятно. Но надо пробовать

Функциям можно передавать аргументы, как другим программам: f() { echo «My arguments: $*»; } f 1 2 3 «4 5 6»

Тогда я не пойму для чего нужно это

vachicul
() автор топика
Последнее исправление: vachicul (всего исправлений: 1)
Ответ на: комментарий от vachicul

$? - это и есть возврат переменной в предыдущее состояние?

Нет, $? - это код возврата, число от 0 (успех) до 255. Его же проверяют if и while.

Всё очень просто, программа выводит список в указанном каталоге (из своей базы данных):

$ program ls /parent/path
entry2
path2/
path3/

Понял, эти пути не связаны напрямую с файловой системой. Тогда тестом на каталог может быть echo "$string" | grep -q '/$': «есть ли прямой слеш на конце?»

Посмотрите ещё на переменную IFS, чтобы разбивать вывод программы по переводам на новую строку, а не по всем пробельным символам.

Функциям можно передавать аргументы, как другим программам: f() { echo "My arguments: $*"; }; f 1 2 3 "4 5 6"

Тогда я не пойму для чего нужно это

Чтобы можно было передать информацию внутрь функции. Локальные переменные живут только в течение текущего вызова функции:

f() {
 local foo
 if [ $# -eq 0 ]; then
  foo=1 # можно предположить, что внутренний вызов функции увидит $foo...
  f "argument" # (а вот аргументы передаются напрямую)
 else
  echo "argument is '$1'; foo is '$foo'" # ...но это не так, foo - пустая строка
 fi
}
f # argument is 'argument'; foo is ''
AITap ★★★★★
()
Ответ на: комментарий от AITap

Третий день ковыряю и ничего не пойму. По запчастям звучит логично, а как собрать ума не приложу. Но даже одна запчасть уже не работает. После завершения вызова return предыдущий вызов функции не выполняет код?

f() {
echo $foo
local foo
read foo # var
if [ -n $foo ]; then
echo "foo is $foo"
else
return
fi
}

Пользователь вводит значение var переменной foo и программа говорит foo is var Если пользователь ничего не вводит, тогда return Разве функция не должна показывать echo $foo предыдущее значение? Как продемонстрировать что переменная вернула значение?

vachicul
() автор топика
Последнее исправление: vachicul (всего исправлений: 1)
Ответ на: комментарий от AITap

choose=$(echo «$list» | sed -n "$var"p) К элементам массива можно обращаться как «${list[$var]}».

Здесь вы неправильно поняли, var это всего лишь номер строки которую вводит пользователь, а choose уже само имя каталога/записи. Поэтому без моего костыля не обойтись, а ваш код исопльзовать уже таким образом "${list[$choose]}"

vachicul
() автор топика