¿Cómo generar múltiples archivos tar.gz anulando archivos específicos para cada entorno?

¿Cómo generar múltiples archivos tar.gz anulando archivos específicos para cada entorno?

Tengo una carpeta raíz Productsy 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}.jsonpero 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.gzarchivo 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 Productsla carpeta raíz tiene tres carpetas de entorno dev: stagey 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 folder1la subcarpeta hay tres subcarpetas más dev, stagey prodexactamente lo mismo para otras subcarpetas folder2y folder3. Cada uno de ellos devy stagela prodsubcarpeta dentro de folder{number}la subcarpeta tendrán archivos que se anularán para ellos.

Necesito generar tres tar.gzarchivos diferentes ahora, uno para cada uno devy stagea prodpartir de la estructura anterior.

  • Cualesquiera que sean los archivos que tenga dentro dev, stageanularán prodlos archivos de sus subcarpetas si también están presentes en su subcarpeta (carpeta1, carpeta2 o carpeta3).
  • Entonces, si files1.jsonestá presente en folder1una subcarpeta y el mismo archivo también está presente dentro de cualquiera de dev, stagey prodluego, 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 stagey otra para proddonde 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.gzy products-prod.gza partir del figure 2cual tendré datos similares figure 3pero 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.gzarchivos 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, folder3tiene carpetas que anulan el entorno, pero no tienen ningún archivo, por lo que en ese caso quiero generar un archivo vacío folder2y folder3tambié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:

  1. muestra la sintaxis de las herramientas GNU. Para BSD finddebes reemplazar -regextype posix-extendedcon just -Ey para BSD tardebes reemplazar --no-recursioncon just -nasí como --transform=s(<- nota el final s) con just-s
  2. Para simplificar la demostración, se supone que el fragmento se ejecuta desde el directorio que contiene Productsy utiliza la $evariable personalizada para el nombre del directorio "entornos" a archivar, mientras que $r es solo una variable auxiliar de nombre corto para contener el Productsnombre.
  3. está entre paréntesis, lo que lo convierte en un subshell, solo para no contaminar su shell $ry $edebe ejecutarlo desde la línea de comandos
  4. 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 ...; donebucle de shell y listo. (posiblemente quitando los paréntesis más externos y rodeando todo el forbucle).

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 findcomandos dobles se alimentan tarprimero 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:

  1. (además de los paréntesis más externos y las variables auxiliares)
  2. el primer findcomando produce solo la lista de archivos no específicos (y directorios principales según su actualización), mientras que el segundo findproduce solo la lista de todos los archivos específicos del entorno.
  3. los dos findcomandos están entre paréntesis por sí mismos para que ambas salidas alimenten la tubería en tarsecuencia
  4. tarlee dicha tubería para obtener los nombres de los archivos y coloca esos archivos en el archivo al mismo tiempo que --transformmodifica sus nombres eliminando el componente "entornos" (si está presente) del nombre de ruta de cada archivo.
  5. los dos findcomandos se separan en lugar de ser solo uno, y se ejecutan uno tras otro, de modo que los archivos no específicos se producen (para tarconsumir) 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:

  1. Todo lo que dijimos anteriormente sobre las sintaxis de GNU y BSD findse taraplica aquí también.
  2. 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.
  3. Estoy usando GNU sedaquí para lidiar con E/S delimitadas por nulos (opción -z), pero puedes reemplazar fácilmente esos dos sedcomandos con, por ejemplo, un while 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, GNU gawkpuede hacerlo); vea a continuación un reemplazo usando bucles Bash
  4. Utilizo uno solo findaquí, ya que no confío en ningún comportamiento implícito detar
  5. Los sedcomandos manipulan la lista de nombres, allanando el camino para los sortcomandos.
  6. específicamente, el primero sedmueve el nombre de "entornos" al comienzo de la ruta, y también le antepone un 0nú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 con 1el propósito de clasificación
  7. dicha preparación normaliza la lista de nombres en los "ojos" de los sortcomandos, 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 para sortlas definiciones de las claves.
  8. el primero sortaplica 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 de 0o 1como lo marcó previamente el sedcomando, garantizando así que cualquier archivo específico de "entornos", cuando esté presente, venga antes de su contraparte no específica
  9. el segundo sortse 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
  10. finalmente, un segundo seddeshace lo hecho por el primero, remodelando así los nombres de los archivos para tararchivarlos.

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 sedcomando 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 sedcomando:

(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 -cpieza posterior xargsdebe convertirse en bash -c.

Respuesta2

solución general

  1. Haga una copia del árbol de directorios. Vincula los archivos para ahorrar espacio.
  2. Modifica la copia. (En el caso de enlaces físicos, necesita saber qué puede hacer de forma segura. Consulte a continuación).
  3. Archive la copia.
  4. Retire la copia.
  5. 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.

  1. hacer una copia

    cdal directorio principal de Products. Este directorio Productsy todo lo que contiene debe pertenecer a un único sistema de archivos. Cree un directorio temporal y vuelva a crearlo Productsallí:

    mkdir -p tmp
    cp -la Products/ tmp/
    
  2. 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 deven 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 a argument 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 deve innecesario prod;stageycualquier otro directorio a esta profundidad.

  3. Archivando la copia

    # still in tmp/Products because of the previous step
    cd ..
    tar cvzf "products-$dname.tgz" Products
    
  4. Quitando la copia

    # now in tmp because of the previous step
    rm -rf Products
    
  5. 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.

Productsse da como argumento. Las palabras clave dev prod stageestán codificadas dentro del script (pero se pueden cambiar fácilmente)

Nota: esto es específico --transformy -print0 -zde 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. tardescenderá 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.jsondesafortunadamente -Xno 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 echoantes tary 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

información relacionada