
Tengo una carpeta raíz Products
y luego un montón de subcarpetas dentro de ella. Cada una de esas subcarpetas tiene un montón de archivos a partir de ahora. Solo por simplicidad, se me ocurrió el nombre de las subcarpetas folder{number}
y el nombre de los archivos, files{number}.json
pero en general tienen nombres diferentes.
En general, tengo 20 subcarpetas diferentes dentro de la carpeta raíz y cada subcarpeta tiene alrededor de 30 archivos como máximo.
(Figura 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
Ahora estoy comprimiendo todo esto en un tar.gz
archivo ejecutando el siguiente comando:
tar cvzf ./products.tgz Products
Pregunta:-
Obtuve un nuevo diseño como se muestra a continuación, donde cada subcarpeta dentro de Products
la carpeta raíz tiene tres carpetas de entorno dev
: stage
y prod
.
(Figura 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
Por ejemplo: dentro de folder1
la subcarpeta hay tres subcarpetas más dev
, stage
y prod
exactamente lo mismo para otras subcarpetas folder2
y folder3
. Cada uno de ellos dev
y stage
la prod
subcarpeta dentro de folder{number}
la subcarpeta tendrán archivos que se anularán para ellos.
Necesito generar tres tar.gz
archivos diferentes ahora, uno para cada uno dev
y stage
a prod
partir de la estructura anterior.
- Cualesquiera que sean los archivos que tenga dentro
dev
,stage
anularánprod
los archivos de sus subcarpetas si también están presentes en su subcarpeta (carpeta1, carpeta2 o carpeta3). - Entonces, si
files1.json
está presente enfolder1
una subcarpeta y el mismo archivo también está presente dentro de cualquiera dedev
,stage
yprod
luego, mientras empaqueto, necesito usar lo que esté presente en su carpeta de entorno y anular sus archivos de subcarpeta; de lo contrario, simplemente use lo que esté presente en su subcarpeta. carpeta(s).
Al final, tendré 3 estructuras diferentes como esta: una para dev
, otra para stage
y otra para prod
donde la carpeta1 (o 2 y 3) tendrá archivos de acuerdo con lo que tengo en su entorno como primera preferencia, ya que están anulados y otros archivos que son no anulado.
(figura 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
Y necesito generar products-dev.gz
, products-stage.gz
y products-prod.gz
a partir del figure 2
cual tendré datos similares figure 3
pero específicos de cada entorno. La única diferencia es que cada subcarpeta, la carpeta 1 (2 o 3) tendrá archivos que se anularán para ellos como primera preferencia desde su carpeta de entorno particular y el resto se usará solo desde su subcarpeta.
¿Es esto posible hacerlo a través de algunos comandos de Linux? La única confusión que tengo es cómo sobrescribir archivos de entorno específicos dentro de una subcarpeta particular y luego generar 3 tar.gz
archivos diferentes en ellos.
Actualizar:
Considere también casos como el siguiente:
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
Como puede ver folder2
, folder3
tiene carpetas que anulan el entorno, pero no tienen ningún archivo, por lo que en ese caso quiero generar un archivo vacío folder2
y folder3
también específico para cada entorno tar.gz
.
Respuesta1
Puede haber muchas formas, aunque todas requieren algún tipo de complejidad para poder manejar el caso de anulación.
Como resumen, aunque un poco largo, podría hacer esto para una iteración, es decir, un directorio de "entornos":
(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%')
desglosado para observarlo mejor:
(
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%'
)
Cosas a tener en cuenta:
- muestra la sintaxis de las herramientas GNU. Para BSD
find
debes reemplazar-regextype posix-extended
con just-E
y para BSDtar
debes reemplazar--no-recursion
con just-n
así como--transform=s
(<- nota el finals
) con just-s
- Para simplificar la demostración, se supone que el fragmento se ejecuta desde el directorio que contiene
Products
y utiliza la$e
variable personalizada para el nombre del directorio "entornos" a archivar, mientras que$r
es solo una variable auxiliar de nombre corto para contener elProducts
nombre. - está entre paréntesis, lo que lo convierte en un subshell, solo para no contaminar su shell
$r
y$e
debe ejecutarlo desde la línea de comandos - no copia ni vincula ni hace referencia a los archivos originales, maneja cualquier nombre de archivo válido, no tiene restricciones de memoria y puede manejar cualquier cantidad de nombres; la única suposición se refiere a los dos primeros niveles de la jerarquía de directorios, ya que cualquier directorio directamente debajo del primer nivel se considera un directorio de "entornos" y, por lo tanto, se ignora (excepto el indicado en
$e
)
Simplemente podría incluir ese fragmento en un for e in dev prod stage; do ...; done
bucle de shell y listo. (posiblemente quitando los paréntesis más externos y rodeando todo el for
bucle).
Lo bueno es que, después de todo, es bastante corto y relativamente simple.
La desventaja es que siempre archiva también.todoelanuladoarchivos (es decir, los básicos), el truco es simplemente que los find
comandos dobles se alimentan tar
primero con los archivos que se van a anular y, por lo tanto, durante la extracción serán sobrescritos por los archivos anulados (es decir, los específicos de los "entornos"). Esto lleva a que un archivo más grande requiera más tiempo tanto durante la creación como durante la extracción, y podría ser indeseable dependiendo de si dicha "gastos generales" puede ser insignificante o no.
Ese conducto descrito en prosa es:
- (además de los paréntesis más externos y las variables auxiliares)
- el primer
find
comando produce solo la lista de archivos no específicos (y directorios principales según su actualización), mientras que el segundofind
produce solo la lista de todos los archivos específicos del entorno. - los dos
find
comandos están entre paréntesis por sí mismos para que ambas salidas alimenten la tubería entar
secuencia tar
lee dicha tubería para obtener los nombres de los archivos y coloca esos archivos en el archivo al mismo tiempo que--transform
modifica sus nombres eliminando el componente "entornos" (si está presente) del nombre de ruta de cada archivo.- los dos
find
comandos se separan en lugar de ser solo uno, y se ejecutan uno tras otro, de modo que los archivos no específicos se producen (paratar
consumir) antes que los archivos específicos del entorno, lo que habilita el truco que describí anteriormente.
Para evitar los gastos generales de incluirsiempre todoslos archivos necesitamos complejidad adicional para poder purgar realmente los archivos anulados. Una forma podría ser la siguiente:
# 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%'
)
Varias cosas a tener en cuenta:
- Todo lo que dijimos anteriormente sobre las sintaxis de GNU y BSD
find
setar
aplica aquí también. - Al igual que la solución anterior, no tiene restricciones de ningún tipo además de la suposición sobre los dos primeros niveles de la jerarquía de directorios.
- Estoy usando GNU
sed
aquí para lidiar con E/S delimitadas por nulos (opción-z
), pero puedes reemplazar fácilmente esos dossed
comandos con, por ejemplo, unwhile read ...
bucle de shell (se requeriría Bash versión 3 o superior) u otro idioma en el que te sientas seguro. con, la única recomendación es que la herramienta que utilice sea capaz de manejar E/S delimitadas por nulos (por ejemplo, GNUgawk
puede hacerlo); vea a continuación un reemplazo usando bucles Bash - Utilizo uno solo
find
aquí, ya que no confío en ningún comportamiento implícito detar
- Los
sed
comandos manipulan la lista de nombres, allanando el camino para lossort
comandos. - específicamente, el primero
sed
mueve el nombre de "entornos" al comienzo de la ruta, y también le antepone un0
número de ayuda solo para que se ordene antes de los archivos que no son de entornos, ya que les estoy anteponiendo a estos últimos un encabezado con1
el propósito de clasificación - dicha preparación normaliza la lista de nombres en los "ojos" de los
sort
comandos, haciendo que todos los nombres sin el nombre de "entornos" y todos tengan la misma cantidad de campos delimitados por barras al principio, lo cual es importante parasort
las definiciones de las claves. - el primero
sort
aplica una clasificación basada primero en los nombres de los archivos, colocando así los mismos nombres uno al lado del otro, y luego por el valor numérico de0
o1
como lo marcó previamente elsed
comando, garantizando así que cualquier archivo específico de "entornos", cuando esté presente, venga antes de su contraparte no específica - el segundo
sort
se fusiona (opción-u
) en los nombres de los archivos dejando solo el primero de los nombres duplicados, que debido al reordenamiento previo siempre es un archivo específico de "entornos" cuando está presente - finalmente, un segundo
sed
deshace lo hecho por el primero, remodelando así los nombres de los archivos paratar
archivarlos.
Si tiene curiosidad por explorar las piezas intermedias de un proceso tan largo, tenga en cuenta que todas funcionan connulo-Nombres delimitados y, por tanto, no se muestran bien en la pantalla. Puede canalizar cualquiera de las salidas intermedias (es decir, quitar al menos el tar
) a una cortesía tr '\0' '\n'
para mostrar una salida amigable para los humanos, solo recuerde que los nombres de archivos con nuevas líneas ocuparán dos líneas en la pantalla.
Se podrían realizar varias mejoras, ciertamente convirtiéndolo en una función/script completamente parametrizado o, por ejemplo, detectando automáticamente cualquier nombre arbitrario para directorios de "entornos", como se muestra a continuación:
Importante: preste atención a los comentarios ya que es posible que no sean bien aceptados por un shell interactivo
(
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 {}
)
Ejemplo de reemplazo del primer sed
comando con un bucle 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)
Para el segundo sed
comando:
(IFS=/; while read -ra parts -d $'\0'; do
printf %s "${parts[*]:2:2}" "/${parts[1]:+${parts[1]}/}" "${parts[*]:4}"
printf \\0
done)
Ambos fragmentos requieren los paréntesis circundantes para poder ser reemplazos directos de sus respectivos sed
comandos dentro del proceso anterior y, por supuesto, la sh -c
pieza posterior xargs
debe convertirse en bash -c
.
Respuesta2
solución general
- Haga una copia del árbol de directorios. Vincula los archivos para ahorrar espacio.
- Modifica la copia. (En el caso de enlaces físicos, necesita saber qué puede hacer de forma segura. Consulte a continuación).
- Archive la copia.
- Retire la copia.
- Repita (modificando de manera diferente) si es necesario.
Ejemplo
Limitaciones:
- este ejemplo utiliza opciones que no son POSIX (probadas en Debian 10),
- hace algunas suposiciones sobre el árbol de directorios,
- puede fallar si hay demasiados archivos.
Trátelo como una prueba de concepto, ajústelo a sus necesidades.
hacer una copia
cd
al directorio principal deProducts
. Este directorioProducts
y todo lo que contiene debe pertenecer a un único sistema de archivos. Cree un directorio temporal y vuelva a crearloProducts
allí:mkdir -p tmp cp -la Products/ tmp/
Modificando la copia
Los archivos en los dos árboles de directorios están vinculados. Si modificas sucontenidoluego alterarás los datos originales. Las operaciones que modifican información contenida en directorios son seguras, no alterarán los datos originales si se realizan en el otro árbol. Estos son:
- eliminar archivos,
- cambiar el nombre de archivos,
- mover archivos (esto incluye mover un archivo sobre otro archivo con
mv
), - creando archivos totalmente independientes.
En su caso, para cada directorio nombrado
dev
en la profundidad correcta, mueva su contenido un nivel hacia arriba:cd tmp/Products dname=dev find . -mindepth 2 -maxdepth 2 -type d -name "$dname" -exec sh -c 'cd "$1" && mv -f -- * ../' sh {} \;
Notas:
mv -- * ../
es propenso aargument list too long
,- de forma predeterminada
*
no coincide con los archivos de puntos.
Luego elimine directorios:
find . -mindepth 2 -maxdepth 2 -type d -exec rm -rf {} +
Tenga en cuenta que esto elimina el , ahora vacío
dev
e innecesarioprod
;stage
ycualquier otro directorio a esta profundidad.Archivando la copia
# still in tmp/Products because of the previous step cd .. tar cvzf "products-$dname.tgz" Products
Quitando la copia
# now in tmp because of the previous step rm -rf Products
repitiendo
Vuelva al directorio correcto y comience de nuevo, esta vez con
dname=stage
; etcétera.
Guión de ejemplo (rápido y sucio)
#!/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
Respuesta3
Lo hice un poco más genérico y trabajé en nombres de archivos no triviales sin cambiar los directorios de origen.
Products
se da como argumento. Las palabras clave dev prod stage
están codificadas dentro del script (pero se pueden cambiar fácilmente)
Nota: esto es específico --transform
y -print0
-z
de extensión de GNU.
ejecutar el script
./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
Es posible que observe duplicados dentro del archivo. tar
descenderá recursivamente de directorios, al restaurar los archivos más profundosSobrescribirarchivos en el directorio principal
Sin embargo, eso necesita más pruebas contra un comportamiento consistente (no estoy seguro de eso). la forma correcta sería excluir files1.json
+ files5.json
desafortunadamente -X
no funciona con--null
Si no confía en ese comportamiento o no desea archivos duplicados en los archivos, puede agregar alguna exclusión para nombres de archivos simples.descomentarel código de arriba tar
. Se permiten nuevas líneas y espacios en blanco en los nombres de archivos, pero se excluirán con un comodín ?
en el patrón de exclusión, lo que en teoría podría excluir más archivos de los esperados (si hay archivos similares que coincidan con ese patrón).
puedes colocar un echo
antes tar
y verás que el script genera los siguientes comandos
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