¿Cómo puedo aplanar un directorio en el siguiente formato?
Antes de: ./aaa/bbb/ccc.png
Después: ./aaa-bbb-ccc.png
./foo/bar/baz.png
y ./foo-bar-baz.png
ambos existen. ¿Asumo que no quieres reemplazar el último con el primero?
¿Cómo puedo aplanar un directorio en el siguiente formato?
Antes de: ./aaa/bbb/ccc.png
Después: ./aaa-bbb-ccc.png
./foo/bar/baz.png
y ./foo-bar-baz.png
ambos existen. ¿Asumo que no quieres reemplazar el último con el primero?
Respuestas:
Advertencia: escribí la mayoría de estos comandos directamente en mi navegador. Advertencia lector.
Con zsh y zmv :
zmv -o -i -Qn '(**/)(*)(D)' '${1//\//-}$2'
Explicación: El patrón **/*
coincide con todos los archivos en subdirectorios del directorio actual, de forma recursiva (no coincide con los archivos del directorio actual, pero no es necesario cambiarles el nombre). Los primeros dos pares de paréntesis son grupos a los que se puede hacer referencia como $1
y $2
en el texto de reemplazo. El par final de paréntesis agrega el D
calificador global para que no se omitan los archivos de puntos. -o -i
significa pasar la -i
opción a mv
para que se le pregunte si se sobrescribirá un archivo existente.
Con solo herramientas POSIX:
find . -depth -exec sh -c '
for source; do
case $source in ./*/*)
target="$(printf %sz "${source#./}" | tr / -)";
mv -i -- "$source" "${target%z}";;
esac
done
' _ {} +
Explicación: la case
declaración omite el directorio actual y los subdirectorios de nivel superior del directorio actual. target
contiene el nombre del archivo de origen ( $0
) con el principal ./
despojado y todas las barras reemplazadas por guiones, más un final z
. La final z
está allí en caso de que el nombre del archivo termine con una nueva línea: de lo contrario, la sustitución del comando lo eliminaría.
Si tu find
no es compatible -exec … +
(OpenBSD, te estoy mirando):
find . -depth -exec sh -c '
case $0 in ./*/*)
target="$(printf %sz "${0#./}" | tr / -)";
mv -i -- "$0" "${target%z}";;
esac
' {} \;
Con bash (o ksh93), no necesita llamar a un comando externo para reemplazar las barras por guiones, puede usar la expansión del parámetro ksh93 con la construcción de reemplazo de cadena ${VAR//STRING/REPLACEMENT}
:
find . -depth -exec bash -c '
for source; do
case $source in ./*/*)
source=${source#./}
target="${source//\//-}";
mv -i -- "$source" "$target";;
esac
done
' _ {} +
for source
ejemplos pierden el primer archivo / directorio presentado ... Finalmente descubrí por qué usted (normalmente) ha utilizado _
como $0
:) ... y gracias por las excelentes respuestas. Aprendo mucho de ellos.
Si bien ya hay buenas respuestas aquí, encuentro esto más intuitivo, dentro de bash
:
find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;
¿Dónde aaa
está tu directorio actual? Los archivos que contiene se moverán al directorio actual (dejando solo directorios vacíos en aaa). Las dos llamadas para tr
manejar tanto directorios como espacios (sin lógica para las barras oblicuas escapadas, un ejercicio para el lector).
Considero que esto es más intuitivo y relativamente fácil de modificar. Es decir, podría cambiar los find
parámetros si digo que solo quiero encontrar archivos .png. Puedo cambiar las tr
llamadas o agregar más según sea necesario. Y puedo agregar echo
antes mv
si quiero verificar visualmente que hará lo correcto (luego repetir sin el extra echo
solo si las cosas se ven bien:
find aaa -name \*.png -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); echo mv "{}" "$new"' \;
Tenga en cuenta también que estoy usando find aaa
... y no find .
... o find aaa/
... ya que los dos últimos dejarían artefactos divertidos en el nombre del archivo final.
find . -mindepth 2 -type f -name '*' |
perl -l000ne 'print $_; s/\//-/g; s/^\.-/.\// and print' |
xargs -0n2 mv
Nota: esto no funcionará para el nombre de archivo que contiene \n
.
Esto, por supuesto, solo mueve f
archivos de tipo ...
Los únicos conflictos de nombres serían de archivos preexistentes en el pwd
Probado con este subconjunto básico
rm -fr junk
rm -f junk*hello*
mkdir -p junk/junkier/junkiest
touch 'hello hello'
touch 'junk/hello hello'
touch 'junk/junkier/hello hello'
touch 'junk/junkier/junkiest/hello hello'
Resultando en
./hello hello
./junk-hello hello
./junk-junkier-hello hello
./junk-junkier-junkiest-hello hello
Aquí hay una ls
versión ... comentarios bienvenidos. Funciona para nombres de archivo como este
"j1ळ\n\001\n/j2/j3/j4/\nh h\n"
, pero estoy realmente interesado en saber de cualquier escollo. Es más un ejercicio de "¿pueden los difamados ls
realmente hacerlo (con firmeza)?"
ls -AbQR1p * | # paths in "quoted" \escaped format
sed -n '/":$/,${/.[^/]$/p}' | # skip files in pwd, blank and dir/ lines
{ bwd="$PWD"; while read -n1; # save base dir strip leading "quote
read -r p; do # read path
printf -vs "${p%\"*}" # strip trailing quote"(:)
[[ $p == *: ]] && { # this is a directory
cd "$bwd/$s" # change into new directory
rnp="${s//\//-}"- # relative name prefix
} || mv "$s" "$bwd/$rnp$s"
done; }
Simplemente canalice el nombre de archivo + ruta al tr
comando.tr <replace> <with>
for FILE in `find aaa -name "*.png"`
do
NEWFILE=`echo $FILE | tr '/' '-'`
cp -v $FILE $NEWFILE
done
cp
línea para cp -v "$FILE" "$NEWFILE"
ayudar? (Además, no debe asumir solo archivos png.)
cp
línea es insuficiente. Todo el enfoque de analizar la find
salida con un for
bucle es defectuoso. Las únicas formas seguras de usar find
son con -exec
, o -print0
si están disponibles (menos portátiles que -exec
). Sugeriría una alternativa más sólida, pero su pregunta está subespecificada en este momento.
Seguro para espacios en nombres de archivo:
#!/bin/bash
/bin/find $PWD -type f | while read FILE
do
_new="${FILE//\//-}"
_new="${_new:1}"
#echo $FILE
#echo $_new
/bin/mv "$FILE" "$_new"
done
Corrió con el mío en cp
lugar de mv
por razones obvias, pero debería producir lo mismo.
type find
-> find is /usr/bin/find
en mi máquina. Usar PATH
como nombre de variable en un script es una idea terrible. Además, su secuencia de comandos manipula los nombres de los archivos con espacios o barras diagonales inversas, ya que utiliza incorrectamente el read
comando (busque en este sitio IFS read -r
).
find is hashed (/bin/find)
, relevante cómo? Diferentes distribuciones son diferentes?
PATH
variable de entorno, que es algo que todo sistema Unix usa. Si escribe /bin/find
, su script está realmente roto en cada sistema (Gilles, el mío y probablemente el OP) que no tiene /bin/find
(por ejemplo, b / c está en /usr/bin/find
). El objetivo de la PATH
variable de entorno es hacer que esto no sea un problema.
$(pwd)
ya está disponible en una variable: $PWD
. Pero debe no utilizar una ruta absoluta aquí: si el script se invoca desde, digamos, /home/tim
, intenta cambiar el nombre /home/tim/some/path
a /home-tim-some-path
.