
У меня есть корневая папка Products
и куча подпапок внутри нее. В каждой из этих подпапок сейчас куча файлов. Просто для простоты я придумал названия подпапок как folder{number}
и имена файлов как, files{number}.json
но в целом у них разные названия.
Обычно у меня внутри корневой папки имеется 20 различных подпапок, и в каждой подпапке содержится около 30 файлов максимум.
(Рисунок 1)
Products
├── folder1
│ ├── files1.json
│ ├── files2.json
│ └── files3.json
├── folder2
│ ├── files4.json
│ ├── files5.json
│ └── files6.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
Теперь я сжимаю все это в tar.gz
файл, выполнив следующую команду:
tar cvzf ./products.tgz Products
Вопрос:-
Я получил новый дизайн, как показано ниже, где каждая подпапка внутри Products
корневой папки содержит три папки среды — dev
, stage
и prod
.
(фигура 2)
Products
├── folder1
│ ├── dev
│ │ └── files1.json
│ ├── files1.json
│ ├── files2.json
│ ├── files3.json
│ ├── prod
│ │ └── files1.json
│ └── stage
│ └── files1.json
├── folder2
│ ├── dev
│ │ └── files5.json
│ ├── files4.json
│ ├── files5.json
│ ├── files6.json
│ ├── prod
│ │ └── files5.json
│ └── stage
│ └── files5.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
Например - Внутри folder1
подпапки есть еще три подпапки dev
, stage
и prod
и то же самое для других подпапок folder2
и folder3
. Каждая из них dev
, stage
и prod
подпапка внутри folder{number}
подпапки будут иметь файлы, которые для них переопределены.
Теперь мне нужно сгенерировать три разных tar.gz
файла — по одному для каждого dev
, stage
и prod
на основе указанной выше структуры.
- Какие бы файлы у меня ни были внутри
dev
,stage
ониprod
также перезапишут файлы своих подпапок, если они присутствуют в их подпапках (folder1, folder2 или folder3). - Таким образом, если
files1.json
присутствует вfolder1
подпапке и тот же файл также присутствует внутри любой изdev
,stage
тоprod
при упаковке мне нужно использовать то, что присутствует в их папке окружения, и переопределить файлы их подпапок, в противном случае просто использовать то, что присутствует в их подпапках.
В итоге у меня будет 3 разных структуры, подобных этой — одна для dev
, одна для stage
и еще одна для prod
, где папка 1 (или 2 и 3) будет содержать файлы в соответствии с тем, что у меня есть в их среде в качестве первого предпочтения, поскольку они переопределяются, и другие файлы, которые не переопределяются.
(рисунок 3)
Products
├── folder1
│ ├── files1.json
│ ├── files2.json
│ └── files3.json
├── folder2
│ ├── files4.json
│ ├── files5.json
│ └── files6.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
И мне нужно сгенерировать products-dev.gz
, products-stage.gz
и products-prod.gz
из figure 2
которых будут данные, подобные, figure 3
но специфичные для каждой среды. Единственное отличие в том, что каждая подпапка folder1 (2 или 3) будет иметь файлы, которые переопределяются для них как первое предпочтение из их конкретной папки среды, а остальные будут использовать только из их подпапки.
Можно ли это сделать с помощью некоторых команд Linux? Единственное, что меня смущает, это как перезаписать определенные файлы среды внутри определенной подпапки, а затем сгенерировать tar.gz
в них 3 разных файла.
Обновлять:
Также рассмотрите такие случаи, как следующие:
Products
├── folder1
│ ├── dev
│ │ ├── files1.json
│ │ └── files5.json
│ ├── files1.json
│ ├── files2.json
│ ├── files3.json
│ ├── prod
│ │ ├── files10.json
│ │ └── files1.json
│ └── stage
│ └── files1.json
├── folder2
│ ├── dev
│ ├── prod
│ └── stage
└── folder3
├── dev
├── prod
└── stage
Как вы можете видеть folder2
, folder3
есть папки переопределения среды, но в них нет никаких файлов, поэтому в этом случае я хочу сгенерировать пустой folder2
и folder3
также в каждой среде определенный tar.gz
файл.
решение1
Способов может быть много, хотя все они требуют некоторой сложности для обработки случая переопределения.
В качестве однострочного кода, хотя и немного длинного, можно сделать следующее для одной итерации, т.е. для одного каталога «environments»:
(r=Products; e=stage; (find -- "$r" -regextype posix-extended -maxdepth 2 \( -regex '^[^/]+(/[^/]+)?' -o ! -type d \) -print0; find -- "$r" -mindepth 1 -path "$r/*/$e/*" -print0) | tar --null --no-recursion -czf "$r-$e.tgz" -T- --transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%')
разбить на части, чтобы лучше рассмотреть:
(
r=Products; e=stage
(
find -- "$r" -regextype posix-extended -maxdepth 2 \( -regex '^[^/]+(/[^/]+)?' -o ! -type d \) -print0
find -- "$r" -mindepth 1 -path "$r/*/$e/*" -print0
) \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'
)
На что следует обратить внимание:
- он показывает синтаксис инструментов GNU. Для BSD
find
вы должны заменить-regextype posix-extended
на just-E
и для BSDtar
вы должны заменить--no-recursion
на just,-n
а также--transform=s
(<- обратите внимание на финалs
) на just-s
- Для простоты демонстрации предполагается, что фрагмент запускается из каталога, содержащего
Products
, и использует пользовательскую$e
переменную для имени каталога "environments" для архивации, в то время как$r
является просто вспомогательной переменной с коротким именем, содержащейProducts
имя - он заключен в скобки, что делает его подоболочкой, просто чтобы не засорять вашу оболочку,
$r
и$e
если вы запускаете его из командной строки - он не копирует и не ссылается на исходные файлы, он обрабатывает любые допустимые имена файлов, у него нет ограничений по памяти, и он может обрабатывать любое количество имен; единственное предположение касается первых двух уровней иерархии каталогов, поскольку любой каталог, расположенный непосредственно под первым уровнем, считается каталогом «окружения» и, таким образом, игнорируется (за исключением указанного в
$e
).
Вы можете просто заключить этот фрагмент в for e in dev prod stage; do ...; done
цикл оболочки и просто продолжить (возможно, убрав внешние скобки и окружив ими весь for
цикл).
Плюс в том, что он довольно короткий и относительно простой.
Недостатком является то, что он всегда архивирует такжевсе the переопределенофайлы (т. е. базовые), трюк в том, что двойные find
команды tar
сначала подают файлы, которые должны быть переопределены, и, следовательно, во время извлечения они будут перезаписаны переопределяющими файлами (т. е. файлами, специфичными для "сред"). Это приводит к тому, что архив большего размера занимает больше времени как при создании, так и при извлечении, и может быть нежелательным в зависимости от того, можно ли пренебречь такими "издержками" или нет.
Этот трубопровод, описанный в прозе, выглядит следующим образом:
- (кроме внешних скобок и вспомогательных переменных)
- первая
find
команда создает только список неспецифичных файлов (и ведущих каталогов в соответствии с вашим обновлением), тогда как втораяfind
создает только список всех файлов, специфичных для среды. - эти две
find
команды заключены в скобки, так что обе их выходные данные подаются в каналtar
последовательно tar
считывает такой канал, чтобы получить имена файлов, и помещает эти файлы в архив, одновременно--transform
удаляя компонент «environments» (если он присутствует) из пути к каждому файлу- эти две
find
команды разделены, а не являются одной, и они запускаются одна за другой, так что неспецифические файлы создаются (дляtar
использования) до специфичных для среды файлов, что позволяет использовать трюк, описанный мной ранее.
Чтобы избежать накладных расходов, связанных с включениемвсегда всефайлы нам нужна дополнительная сложность, чтобы действительно очистить переопределенные файлы. Один из способов может быть таким:
# still a pipeline, but this time I won't even pretend it to be a one-liner
(
r=Products; e=stage; LC_ALL=C
find -- "$r" -regextype posix-extended \( -path "$r/*/$e/*" -o \( -regex '^([^/]+/){2}[^/]+' ! -type d \) -o -regex '^[^/]+(/[^/]+)?' \) -print0 \
| sed -zE '\%^(([^/]+/){2})([^/]+/)%s%%0/\3\1%;t;s%^%1//%' \
| sort -zt/ -k 3 -k 1,1n \
| sort -zut/ -k 3 \
| sed -zE 's%^[01]/(([^/]+/)|/)(([^/]+/?){2})%\3\2%' \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'
)
Следует отметить несколько моментов:
- все, что мы сказали ранее относительно синтаксисов GNU и BSD для
find
иtar
применимо и здесь - как и предыдущее решение, оно не имеет никаких ограничений, кроме предположения о первых двух уровнях иерархии каталогов.
- Я использую GNU
sed
здесь для работы с вводом-выводом с разделителями-нулями (опция-z
), но вы можете легко заменить эти двеsed
команды, например,while read ...
циклом оболочки (требуется Bash версии 3 или выше) или другим языком, в котором вы уверены, единственная рекомендация — чтобы инструмент, который вы используете, мог обрабатывать ввод-вывод с разделителями-нулями (например, GNUgawk
может это сделать); см. ниже замену с использованием циклов Bash. - Я использую здесь один сингл
find
, так как не полагаюсь на какое-либо подразумеваемое поведениеtar
- Команды
sed
манипулируют списком имен, прокладывая путь дляsort
команд - в частности, первый
sed
перемещает имя «среды» в начало пути, также добавляя к нему префикс в виде вспомогательного0
числа, просто чтобы сортировать его перед файлами, не относящимися к средам, так как я добавляю к ним префикс в виде лидирующего числа1
с целью сортировки - Такая подготовка нормализует список имен в «глазах» команд
sort
, делая все имена без имени «окружения» и все имеющие одинаковое количество полей, разделенных косой чертой, в начале, что важно дляsort
определений ключей - первый
sort
применяет сортировку, основанную сначала на именах файлов, таким образом помещая одинаковые имена рядом друг с другом, а затем на числовом значении0
или1
, как отмечено ранее командойsed
, таким образом гарантируя, что любой файл, специфичный для "сред", если он присутствует, будет располагаться перед своим неспецифическим аналогом - второй
sort
объединяет (опция-u
) имена файлов, оставляя только первое из дублирующихся имен, которое из-за предыдущего переупорядочивания всегда является файлом, специфичным для «среды», если оно присутствует - наконец, второй
sed
отменяет то, что было сделано первым, тем самым изменяя имена файлов дляtar
архива
Если вам интересно изучить промежуточные части такого длинного конвейера, имейте в виду, что все они работают снуль-разделенные имена, и поэтому плохо отображаются на экране. Вы можете передать любой из промежуточных выводов (т. е. убрать хотя бы tar
) в любезность, tr '\0' '\n'
чтобы показать вывод, понятный человеку, просто помните, что имена файлов с переводами строк будут занимать две строки на экране.
Можно было бы внести несколько улучшений, например, сделав его полностью параметризованной функцией/скриптом или, например, автоматически определяя любое произвольное имя для каталогов «environments», как показано ниже:
Важный: обратите внимание на комментарии, так как они могут быть не очень хорошо восприняты интерактивной оболочкой
(
export r=Products LC_ALL=C
cd -- "$r/.." || exit
# make arguments out of all directories lying at the second level of the hierarchy
set -- "$r"/*/*/
# then expand all such paths found, take their basenames only, uniquify them, and pass them along xargs down to a Bash pipeline the same as above
printf %s\\0 "${@#*/*/}" \
| sort -zu \
| xargs -0I{} sh -c '
e="${1%/}"
echo --- "$e" ---
find -- "$r" -regextype posix-extended \( -path "$r/*/$e/*" -o \( -regex '\''^([^/]+/){2}[^/]+'\'' ! -type d \) -o -regex '\''^[^/]+(/[^/]+)?'\'' \) -print0 \
| sed -zE '\''\%^(([^/]+/){2})([^/]+/)%s%%0/\3\1%;t;s%^%1//%'\'' \
| sort -zt/ -k 3 -k 1,1n \
| sort -zut/ -k 3 \
| sed -zE '\''s%^[01]/(([^/]+/)|/)(([^/]+/?){2})%\3\2%'\'' \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'\''%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'\''
' packetizer {}
)
Пример замены первой sed
команды с помощью цикла Bash:
(IFS=/; while read -ra parts -d $'\0'; do
if [ "${#parts[@]}" -gt 3 ]; then
env="${parts[2]}"; unset parts[2]
printf 0/%s/%s\\0 "$env" "${parts[*]}"
else
printf 1//%s\\0 "${parts[*]}"
fi
done)
Для второй sed
команды:
(IFS=/; while read -ra parts -d $'\0'; do
printf %s "${parts[*]:2:2}" "/${parts[1]:+${parts[1]}/}" "${parts[*]:4}"
printf \\0
done)
Оба фрагмента требуют окружающих скобок, чтобы они могли быть вставленными заменами для соответствующих sed
команд в конвейере выше, и, конечно, sh -c
часть после них xargs
необходимо преобразовать в bash -c
.
решение2
Общее решение
- Сделайте копию дерева каталогов. Сделайте жесткие ссылки на файлы для экономии места.
- Измените копию. (В случае жестких ссылок вам нужно знать, что вы можете сделать безопасно. См. ниже.)
- Сохраните копию в архиве.
- Удалить копию.
- При необходимости повторите (изменяя по-другому).
Пример
Ограничения:
- в этом примере используются не-POSIX параметры (проверено на Debian 10),
- он делает некоторые предположения о дереве каталогов,
- может произойти сбой, если файлов слишком много.
Относитесь к этому как к доказательству концепции и адаптируйте его к своим потребностям.
Создание копии
cd
в родительский каталогProducts
. Этот каталогProducts
и все, что в нем находится, должны принадлежать к одной файловой системе. Создайте временный каталог и пересоздайтеProducts
там:mkdir -p tmp cp -la Products/ tmp/
Изменение копии
Файлы в двух деревьях каталогов жестко связаны. Если вы измените ихсодержанието вы измените исходные данные. Операции, которые изменяют информацию, хранящуюся в каталогах, безопасны, они не изменят исходные данные, если будут выполнены в другом дереве. Это:
- удаление файлов,
- переименование файлов,
- перемещение файлов (включая перемещение файла поверх другого файла с помощью
mv
), - создание полностью независимых файлов.
В вашем случае для каждого каталога с именем
dev
на нужной глубине переместите его содержимое на один уровень вверх:cd tmp/Products dname=dev find . -mindepth 2 -maxdepth 2 -type d -name "$dname" -exec sh -c 'cd "$1" && mv -f -- * ../' sh {} \;
Примечания:
mv -- * ../
склонен кargument list too long
,- по умолчанию
*
не соответствует dotfiles.
Затем удалите каталоги:
find . -mindepth 2 -maxdepth 2 -type d -exec rm -rf {} +
Обратите внимание, что это удаляет теперь пустые
dev
и ненужныеprod
,stage
;илюбой другой каталог на этой глубине.Архивация копии
# still in tmp/Products because of the previous step cd .. tar cvzf "products-$dname.tgz" Products
Удаление копии
# now in tmp because of the previous step rm -rf Products
Повторяющийся
Вернитесь в нужный каталог и начните заново, на этот раз с
dname=stage
; и так далее.
Пример сценария (быстрый и грубый)
#!/bin/bash
dir=Products
[ -d "$dir" ] || exit 1
mkdir -p tmp
for dname in dev prod stage; do
(
cp -la "$dir" tmp/
cd "tmp/$dir"
[ "$?" -eq 0 ] || exit 1
find . -mindepth 2 -maxdepth 2 -type d -name "$dname" -exec sh -c 'cd "$1" && mv -f -- * ../' sh {} \;
find . -mindepth 2 -maxdepth 2 -type d -exec rm -rf {} +
cd ..
[ "$?" -eq 0 ] || exit 1
tar cvzf "${dir,,}-$dname.tgz" "$dir"
rm -rf "$dir" || exit 1
) || exit "$?"
done
решение3
Я сделал это немного более общим и работаю над нетривиальными именами файлов, фактически не меняя исходные каталоги.
Products
указывается как аргумент. Ключевые слова dev prod stage
жестко закодированы внутри скрипта (но могут быть легко изменены)
Примечание: это специфично для GNU --transform
и -print0
-z
является расширением
запустить скрипт
./script Products
#!/bin/sh
# environment
subdirs="dev prod stage"
# script requires arguments
[ -n "$1" ] || exit 1
# remove trailing /
while [ ${i:-0} -le $# ]
do
i=$((i+1))
dir="$1"
while [ "${dir#"${dir%?}"}" = "/" ]
do
dir="${dir%/}"
done
set -- "$@" "$dir"
shift
done
# search string
for sub in $subdirs
do
[ -n "$search" ] && search="$search -o -name $sub" || search="( -name $sub"
done
search="$search )"
# GNU specific zero terminated handling for non-trivial directory names
excludes="$excludes $(find -L "$@" -type d $search -print0 | sed -z 's,[^/]*/,*/,g' | sort -z | uniq -z | xargs -0 printf '--exclude=%s\n')"
# for each argument
for dir in "$@"
do
# for each environment
[ -e "$dir" ] || continue
for sub in $subdirs
do
# exclude other subdirs
exclude=$(echo "$excludes" | grep -v "$sub")
# # exclude files that exist in subdir (at least stable against newlines and spaces in file names)
# include=$(echo "$excludes" | grep "$sub" | cut -d= -f2)
# [ -n "$include" ] && files=$(find $include -mindepth 1 -maxdepth 1 -print0 | tr '\n[[:space:]]' '?' | sed -z "s,/$sub/,/," | xargs -0 printf '--exclude=%s\n')
# exclude="$exclude $files"
# create tarball archive
archive="${dir##*/}-${sub}.tgz"
[ -f "$archive" ] && echo "WARNING: '$archive' is overwritten"
tar --transform "s,/$sub$,," --transform "s,/$sub/,/," $exclude -czhf "$archive" "$dir"
done
done
Вы можете заметить дубликаты внутри архива. tar
Будет рекурсивно спускаться по каталогам, при восстановлении будут восстановлены более глубокие файлы.перезаписатьфайлы в родительском каталоге
Однако это требует дальнейшего тестирования на соответствие поведению (в этом я не уверен). Правильным способом было бы exlude files1.json
+, files5.json
к сожалению, -X
не работает с--null
Если вы не доверяете такому поведению или не хотите, чтобы в архивах были дубликаты файлов, вы можете добавить исключение для простых имен файлов.раскомментироватькод выше tar
. В именах файлов допускаются символы новой строки и пробелы, но они будут исключены с помощью подстановочного знака ?
в шаблоне исключения, что теоретически может исключить больше файлов, чем ожидалось (если есть похожие файлы, соответствующие этому шаблону)
вы можете поместить echo
перед tar
и вы увидите, что скрипт генерирует следующие команды
tar --transform 's,/dev$,,' --transform 's,/dev/,/,' --exclude=*/*/prod --exclude=*/*/stage -czhf Products-dev.tgz Products
tar --transform 's,/prod$,,' --transform 's,/prod/,/,' --exclude=*/*/dev --exclude=*/*/stage -czhf Products-prod.tgz Products
tar --transform 's,/stage$,,' --transform 's,/stage/,/,' --exclude=*/*/dev --exclude=*/*/prod -czhf Products-stage.tgz Products