[stackoverflow](https://stackoverflow.com/questions/32077318/generating-a-bash-script-from-a-bash-script)

  • Скрипт запускается в корне нужного дерева, проходит по всем каталогам и генерирует другой bash‑скрипт recreate_tree.sh. [stackoverflow](https://stackoverflow.com/questions/32077318/generating-a-bash-script-from-a-bash-script)
  • В recreate_tree.sh есть переменная TARGET_BASE — при запуске её можно указать аргументом (корень, куда восстанавливать структуру), по умолчанию берётся текущий каталог. [stackoverflow](https://stackoverflow.com/questions/32077318/generating-a-bash-script-from-a-bash-script)
  • Для каждого каталога исходного дерева генерируются команды:
  • mkdir -p — создаёт каталог (и родителей, если их ещё нет);
  • chmod — восстанавливает режим доступа (права);
  • chown (если скрипт запущен от root) — восстанавливает владельца и группу по UID/GID;
  • touch -a и touch -m — восстанавливают время доступа и модификации. [linuxize](https://linuxize.com/cheatsheet/tar/tar-cheatsheet.pdf)
  • Дополнительно, если в системе есть getfacl/setfacl, скрипт сохраняет и восстанавливает ACL; если есть getfattr/setfattr, — расширенные атрибуты (xattr). [shellmap.eversources](https://shellmap.eversources.app/task/view-acl-extended-permissions)
  • Сам сгенерированный recreate_tree.sh делается исполняемым с правами 755, чтобы его можно было сразу запускать. [itslinuxfoss](https://itslinuxfoss.com/make-bash-script-executable-using-chmod/)

Итого: первый скрипт — “снимок структуры и метаданных в виде bash”, второй (recreate_tree.sh) — “реплейер”, который на любой базе (TARGET_BASE) создаёт такое же дерево каталогов с максимально полным набором метаинформации. [gnu](https://www.gnu.org/software/tar/manual/html_section/Attributes.html)

######################################################
#!/usr/bin/env bash
set -euo pipefail

out="recreate_tree.sh"
src="$(pwd)"

{
cat <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

TARGET_BASE="${1:-$(pwd)}"

have_chown=0
[[ $EUID -eq 0 ]] && have_chown=1

EOF

Каталоги

find "$src" -depth -type d | while IFS= read -r dir; do
rel="${dir#$src}"

if [[ -z "$rel" ]]; then
t='${TARGET_BASE}'
else
printf -v qrel '%q' "$rel"
t="\${TARGET_BASE}${qrel}"
fi

mode="$(stat -c '%a' "$dir")"
uid="$(stat -c '%u' "$dir")"
gid="$(stat -c '%g' "$dir")"
atime="$(stat -c '%x' "$dir" | sed 's/\.[0-9]\+ .*//')"
mtime="$(stat -c '%y' "$dir" | sed 's/\.[0-9]\+ .*//')"

echo "mkdir -p -- $t"
echo "chmod $mode -- $t"
echo "if [[ \$have_chown -eq 1 ]]; then chown $uid:$gid -- $t; fi"
printf 'touch -a -d %q -- %s\n' "$atime" "$t"
printf 'touch -m -d %q -- %s\n' "$mtime" "$t"
echo
done

Симлинки

find "$src" -type l | while IFS= read -r link; do
rel="${link#$src}"

if [[ -z "$rel" ]]; then
t='${TARGET_BASE}'
else
printf -v qrel '%q' "$rel"
t="\${TARGET_BASE}${qrel}"
fi

link_target="$(readlink "$link")"
printf 'ln -sfn -- %q %s\n' "$link_target" "$t"
echo
done

} > "$out"

chmod 755 "$out"
echo "Создан: $out"

################# вариант задротов ##############

#!/usr/bin/env bash
set -euo pipefail

out="recreate_tree.sh"
src="$(pwd)"

tmp_archive="$(mktemp)"
trap 'rm -f "$tmp_archive"' EXIT

(
cd "$src"
find . -depth -print0 | cpio --null -o -H newc > "$tmp_archive"
)

archive_size="$(wc -c < "$tmp_archive" | tr -d ' ')"

cat > "$out" <<EOF
#!/usr/bin/env bash
set -euo pipefail

TARGET_BASE="\${1:-\$(pwd)}"
SELF="\$0"
ARCHIVE_OFFSET=\$((LINENO + 3))

mkdir -p -- "\$TARGET_BASE"

tail -c +$((archive_size + 1)) /dev/null >/dev/null 2>&1 || true

archive_line=\$(awk '/^__CPIO_ARCHIVE_BELOW__$/ { print NR + 1; exit }' "\$SELF")
if [[ -z "\$archive_line" ]]; then
echo "Ошибка: встроенный архив не найден" >&2
exit 1
fi

tail -n +"\\\$archive_line" "\$SELF" | (
cd "\$TARGET_BASE"
cpio -idm --quiet
)
exit 0
__CPIO_ARCHIVE_BELOW__
EOF

cat "$tmp_archive" >> "$out"
chmod 755 "$out"

echo "Создан: $out"

[file-name 000377_2026-06-20_21-38-58.txt]