Come posso appiattire una directory nel seguente formato?
Prima: ./aaa/bbb/ccc.png
Dopo: ./aaa-bbb-ccc.png
./foo/bar/baz.pnged ./foo-bar-baz.pngentrambi 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.pnged ./foo-bar-baz.pngentrambi 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 $1e $2nel testo sostitutivo. L'ultima coppia di parentesi aggiunge il D qualificatore glob in modo che i file dot non vengano omessi. -o -isignifica passare l' -iopzione a in mvmodo 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' caseistruzione omette la directory corrente e le sottodirectory di livello superiore della directory corrente. targetcontiene 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 findnon 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 sourceesempi 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 aaatua directory esistente. I file che contiene verranno spostati nella directory corrente (lasciando solo le directory vuote in aaa). Le due chiamate per trgestire 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 findparametri se dicessi che voglio trovare solo i file .png. Posso modificare le trchiamate o aggiungerne altre se necessario. E posso aggiungere echoprima mvse voglio verificare visivamente che farà la cosa giusta (quindi ripetere senza extra echosolo 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 ffile 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 lsversione .. 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ò lseffettivamente 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 trcomando.tr <replace> <with>
for FILE in `find aaa -name "*.png"`
do
NEWFILE=`echo $FILE | tr '/' '-'`
cp -v $FILE $NEWFILE
done
cplinea per cp -v "$FILE" "$NEWFILE"aiutare? (Inoltre, non dovresti assumere solo file png.)
cpriga è insufficiente. L'intero approccio di analisi finddell'output con un forloop è difettoso. Gli unici modi sicuri di utilizzo findsono con -exec, o -print0se 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 cpanziché mvper ovvi motivi, ma dovrebbe produrre lo stesso.
type find-> find is /usr/bin/findsulla mia macchina. Usare PATHcome 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 readcomando (cercare in questo sito IFS read -r).
find is hashed (/bin/find), rilevante come? Diverse distro sono diverse?
PATHvariabile d'ambiente, che è qualcosa che ogni sistema Unix utilizza. Se scrivi /bin/findallora 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 PATHvariabile 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/timsi tenta di rinominare /home/tim/some/patha /home-tim-some-path.