Appiattisci directory ma conserva i nomi delle directory nel nuovo nome file


11

Come posso appiattire una directory nel seguente formato?

Prima: ./aaa/bbb/ccc.png

Dopo: ./aaa-bbb-ccc.png


PS: Ti preghiamo di tenere conto anche dei nomi dei file con caratteri dispari (ad es. Spazi)
Nathan JB

3
Dovrai anche specificare come vuoi gestire il possibile caso in cui ./foo/bar/baz.pnged ./foo-bar-baz.pngentrambi esistono. Suppongo che non vuoi sostituire il secondo con il primo?
jw013,

Nel mio caso, è irrilevante, ma la risposta dovrebbe in effetti spiegarlo in modo che altri possano fare affidamento su di esso! Grazie!
Nathan JB,

Risposte:


7

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
' _ {} +

Gli 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.
Peter

Si noti che l'esempio zmv esegue semplicemente una corsa a secco: stampa i comandi di spostamento ma non li esegue. Per eseguire effettivamente quei comandi, rimuovere n in -Qn.
Peter,

5

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.


Grazie per questa fodera - ha funzionato bene per me quando l'ho fatto dall'esterno della directory (che è esattamente quello che hai detto di fare). Quando ho provato a fare dall'interno della directory (sperando di evitare l'aggiunta del nome della directory radice) tutti i file "sono scomparsi". Li avevo trasformati in file nascosti nella stessa directory.
dgig

2
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

0

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; }

-1

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

1
Questo non gestisce in modo sicuro nomi di file dispari. Gli spazi lo spezzeranno (tra le altre cose).
jw013,

A prima vista, questa è una soluzione pulita e leggibile, ma @ jw013 ha ragione, non gestirà bene gli spazi. Cambiare la cplinea per cp -v "$FILE" "$NEWFILE"aiutare? (Inoltre, non dovresti assumere solo file png.)
Nathan JB

2
@ NathanJ.Brauer L'aggiunta di virgolette alla 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.
jw013,

-2

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.


3
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).
Gilles 'SO- smetti di essere malvagio' il

find is hashed (/bin/find), rilevante come? Diverse distro sono diverse?
Tim

Sapevo che avrei dovuto stare lontano da questo, esca per avvoltoi.
Tim

@Tim Puoi sempre eliminare la risposta per riavere il tuo rappresentante (e anche il mio b / c costa il rappresentante per il voto negativo). I percorsi codificati negli script sembrano suggerire che manchi il punto della 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.
jw013,

Tra l'altro, $(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.
Gilles 'SO- smetti di essere malvagio' il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.