Come posso appiattire una directory nel seguente formato?
Prima: ./aaa/bbb/ccc.png
Dopo: ./aaa-bbb-ccc.png
./foo/bar/baz.png
ed ./foo-bar-baz.png
entrambi esistono. Suppongo che non vuoi sostituire il secondo con il primo?
Come posso appiattire una directory nel seguente formato?
Prima: ./aaa/bbb/ccc.png
Dopo: ./aaa-bbb-ccc.png
./foo/bar/baz.png
ed ./foo-bar-baz.png
entrambi esistono. Suppongo che non vuoi sostituire il secondo con il primo?
Risposte:
Avvertenza: ho digitato la maggior parte di questi comandi direttamente nel mio browser. Avvertimento.
Con zsh e zmv :
zmv -o -i -Qn '(**/)(*)(D)' '${1//\//-}$2'
Spiegazione: Il modello **/*
corrisponde a tutti i file nelle sottodirectory della directory corrente, in modo ricorsivo (non corrisponde ai file nella directory corrente, ma questi non devono essere rinominati). Le prime due coppie di parentesi sono gruppi a cui è possibile fare riferimento come $1
e $2
nel testo sostitutivo. L'ultima coppia di parentesi aggiunge il D
qualificatore glob in modo che i file dot non vengano omessi. -o -i
significa passare l' -i
opzione a in mv
modo che venga richiesto se un file esistente verrà sovrascritto.
Con solo strumenti POSIX:
find . -depth -exec sh -c '
for source; do
case $source in ./*/*)
target="$(printf %sz "${source#./}" | tr / -)";
mv -i -- "$source" "${target%z}";;
esac
done
' _ {} +
Spiegazione: l' case
istruzione omette la directory corrente e le sottodirectory di livello superiore della directory corrente. target
contiene il nome del file di origine ( $0
) con il carattere principale ./
rimosso e tutte le barre sostituite da trattini, più un finale z
. Il finale z
è lì nel caso in cui il nome del file termina con una nuova riga: altrimenti la sostituzione del comando lo eliminerebbe.
Se il tuo find
non supporta -exec … +
(OpenBSD, ti sto guardando):
find . -depth -exec sh -c '
case $0 in ./*/*)
target="$(printf %sz "${0#./}" | tr / -)";
mv -i -- "$0" "${target%z}";;
esac
' {} \;
Con bash (o ksh93), non è necessario chiamare un comando esterno per sostituire le barre con trattini, è possibile utilizzare l'espansione del parametro ksh93 con il costrutto di sostituzione della stringa ${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
esempi mancano del primo file / dir presentato ... Ho finalmente scoperto perché tu (normalmente) hai usato _
come $0
:) ... e grazie per le ottime risposte. Imparo così tanto da loro.
Mentre ci sono già buone risposte qui, lo trovo più intuitivo, all'interno di bash
:
find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;
Dov'è la aaa
tua directory esistente. I file che contiene verranno spostati nella directory corrente (lasciando solo le directory vuote in aaa). Le due chiamate per tr
gestire sia le directory che gli spazi (senza logica per le barre sfuggite - un esercizio per il lettore).
Lo considero più intuitivo e relativamente facile da modificare. Cioè potrei cambiare i find
parametri se dicessi che voglio trovare solo i file .png. Posso modificare le tr
chiamate o aggiungerne altre se necessario. E posso aggiungere echo
prima mv
se voglio verificare visivamente che farà la cosa giusta (quindi ripetere senza extra echo
solo se le cose sembrano giuste:
find aaa -name \*.png -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); echo mv "{}" "$new"' \;
Nota anche che sto usando find aaa
... e non find .
... o find aaa/
... poiché gli ultimi due lascerebbero artefatti divertenti nel nome del file finale.
find . -mindepth 2 -type f -name '*' |
perl -l000ne 'print $_; s/\//-/g; s/^\.-/.\// and print' |
xargs -0n2 mv
Nota: questo non funzionerà per il nome file che contiene \n
.
Questo, ovviamente, sposta solo i f
file di tipo ...
L'unico conflitto di nomi verrebbe dai file preesistenti nel pwd
Testato con questo sottoinsieme di base
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'
Con il risultato di
./hello hello
./junk-hello hello
./junk-junkier-hello hello
./junk-junkier-junkiest-hello hello
Ecco una ls
versione .. commenti benvenuti. Funziona con nomi di file bizzarri come questo
"j1ळ\n\001\n/j2/j3/j4/\nh h\n"
, ma sono davvero interessato a conoscere eventuali insidie. È più un esercizio di "il malignato può ls
effettivamente farlo (con fermezza)?"
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; }
Basta reindirizzare il nome file + percorso al tr
comando.tr <replace> <with>
for FILE in `find aaa -name "*.png"`
do
NEWFILE=`echo $FILE | tr '/' '-'`
cp -v $FILE $NEWFILE
done
cp
linea per cp -v "$FILE" "$NEWFILE"
aiutare? (Inoltre, non dovresti assumere solo file png.)
cp
riga è insufficiente. L'intero approccio di analisi find
dell'output con un for
loop è difettoso. Gli unici modi sicuri di utilizzo find
sono con -exec
, o -print0
se disponibili (meno portatili di -exec
). Suggerirei un'alternativa più solida, ma al momento la tua domanda è sotto specifica.
Sicuro per gli spazi nei nomi dei file:
#!/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
Ho corso con il mio cp
anziché mv
per ovvi motivi, ma dovrebbe produrre lo stesso.
type find
-> find is /usr/bin/find
sulla mia macchina. Usare PATH
come nome variabile in uno script è un'idea terribile. Inoltre, lo script modifica i nomi dei file con spazi o barre rovesciate, poiché si utilizza in modo improprio il read
comando (cercare in questo sito IFS read -r
).
find is hashed (/bin/find)
, rilevante come? Diverse distro sono diverse?
PATH
variabile d'ambiente, che è qualcosa che ogni sistema Unix utilizza. Se scrivi /bin/find
allora il tuo script è effettivamente rotto su ogni sistema (Gilles, il mio e probabilmente gli OP) che non ha /bin/find
(ad esempio b / c in cui si trova /usr/bin/find
). L'intero punto della PATH
variabile d'ambiente è rendere questo un problema.
$(pwd)
è già disponibile in una variabile: $PWD
. Ma è necessario non utilizzare un percorso assoluto qui: se lo script viene richiamato da, diciamo, /home/tim
si tenta di rinominare /home/tim/some/path
a /home-tim-some-path
.