Rinominare i file in batch con Bash


101

Come può Bash rinominare una serie di pacchetti per rimuovere i loro numeri di versione? Ho giocato con entrambi expre %%, inutilmente.

Esempi:

Xft2-2.1.13.pkg diventa Xft2.pkg

jasper-1.900.1.pkg diventa jasper.pkg

xorg-libXrandr-1.2.3.pkg diventa xorg-libXrandr.pkg


1
Ho intenzione di usarlo regolarmente, come uno script da scrivere una sola volta. Qualsiasi sistema che userò avrà bash su di esso, quindi non ho paura dei bashismi che sono abbastanza utili.
Jeremy L

Più in generale si veda anche stackoverflow.com/questions/28725333/...
tripleee

Risposte:


190

Potresti usare la funzione di espansione dei parametri di bash

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

Le virgolette sono necessarie per i nomi di file con spazi.


1
Piace anche a me, ma usa caratteristiche specifiche di bash ... normalmente non le uso a favore di sh "standard".
Diego Sevilla

2
Per le cose interattive su una riga usa e getta lo uso sempre al posto di sed-fu. Se fosse una sceneggiatura, sì, evita i bashismi.
richq

Un problema che ho riscontrato con il codice: cosa succede se i file sono già stati rinominati e lo eseguo di nuovo? Ricevo avvisi per il tentativo di spostarmi in una sottodirectory di se stesso.
Jeremy L

1
Per evitare errori di riesecuzione, puoi usare lo stesso modello nella parte " .pkg", cioè "for i in * - [0-9.] .Pkg; do mv $ i $ {i / - [0-9. .] *. pkg / .pkg}; done ". Ma gli errori sono abbastanza innocui (passando allo stesso file).
richq

3
Non è una soluzione bash, ma se hai installato perl, fornisce il pratico comando di rinomina per rinominare in batch, fai semplicementerename "s/-[0-9.]*//" *.pkg
Rufus

33

Se tutti i file si trovano nella stessa directory, la sequenza

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

farà il tuo lavoro. Il comando sed creerà una sequenza di comandi mv , che puoi quindi inserire nella shell. È meglio eseguire prima la pipeline senza il trailing in | shmodo da verificare che il comando faccia quello che vuoi.

Per ricorrere a più directory usa qualcosa di simile

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh

Si noti che in sed la sequenza di raggruppamento delle espressioni regolari è costituita da parentesi quadre precedute da una barra rovesciata \(e \), anziché da singole parentesi (e ).


Perdona la mia ingenuità, ma sfuggire alle parentesi con i backslash non li farebbe essere trattati come caratteri letterali?
devios1

2
In sed la sequenza di raggruppamento delle espressioni regolari è (, piuttosto che una singola parentesi.
Diomidis Spinellis

non ha funzionato per me, sarei felice di ricevere il tuo aiuto .... Il suffisso del file FROM viene aggiunto al file TO: $ find. -tipo d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p' | sh >> >>>>> OUTPUT >>>> mv: rinomina ./base/src/main/java/com/anysoftkeyboard/base/dictionaries in ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : Nessun file o directory di questo tipo
Vitali Pom

Sembra che ti manchi la barra rovesciata tra parentesi.
Diomidis Spinellis

1
Non utilizzare lsnegli script. La prima varietà può essere ottenuta con printf '%s\n' * | sed ...e con un po 'di creatività probabilmente puoi sbarazzartene sedanche tu . Bash printfoffre un '%q'codice di formato che potrebbe tornare utile se si hanno nomi di file che contengono metacaratteri letterali della shell.
tripleee

10

Farò qualcosa di simile:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

suppone che tutti i tuoi file siano nella directory corrente. In caso contrario, prova a utilizzare Trova come consigliato sopra da Javier.

EDIT : Inoltre, questa versione non utilizza alcuna funzionalità specifica di bash, come altre sopra, il che ti porta a una maggiore portabilità.


Sono tutti nella stessa directory. Cosa ne pensate della risposta di rq?
Jeremy L

Mi piace non usare funzionalità specifiche di bash, ma invece sh più standard.
Diego Sevilla

1
Ho pensato che fosse per una rapida ridenominazione, non per scrivere la prossima generazione di applicazioni aziendali portatili e multipiattaforma ;-)
richq

1
Haha, abbastanza giusto ... Sono uscito da un uomo dalla mentalità portabilità come me :)
Diego Sevilla

Questo non tiene conto del terzo esempio: xorg-libXrandr (trattino usato nel nome).
Jeremy L

5

Ecco un POSIX quasi equivalente della risposta attualmente accettata . Questo scambia l' ${variable/substring/replacement}espansione dei parametri solo Bash con una disponibile in qualsiasi shell compatibile con Bourne.

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

L'espansione del parametro ${variable%pattern}produce il valore di variablecon qualsiasi suffisso che corrisponde a patternrimosso. (C'è anche ${variable#pattern}da rimuovere un prefisso.)

Ho mantenuto lo schema secondario -[0-9.]*dalla risposta accettata anche se forse è fuorviante. Non è un'espressione regolare, ma un modello glob; quindi non significa "un trattino seguito da zero o più numeri o punti". Invece, significa "un trattino, seguito da un numero o un punto, seguito da qualsiasi cosa". Il "qualsiasi cosa" sarà la partita più breve possibile, non la più lunga. (Bash offre ##e %%per ritagliare il prefisso o il suffisso più lungo possibile, piuttosto che il più breve.)


5

Possiamo presumere che sedsia disponibile su qualsiasi * nix, ma non possiamo essere sicuri che supporterà la sed -ngenerazione di comandi mv. ( NOTA: solo GNU lo sedfa.)

Anche così, bash builtins e sed, possiamo creare rapidamente una funzione di shell per farlo.

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

uso

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

Creazione di cartelle di destinazione:

Poiché mvnon crea automaticamente cartelle di destinazione, non possiamo utilizzare la nostra versione iniziale di sedrename.

È un cambiamento abbastanza piccolo, quindi sarebbe bello includere questa funzionalità:

Avremo bisogno di una funzione di utilità, abspath(o percorso assoluto) poiché bash non ha questa build.

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

Una volta ottenuto ciò, possiamo generare le cartelle di destinazione per un pattern sed / rename che include una nuova struttura di cartelle.

Ciò garantirà che conosciamo i nomi delle nostre cartelle di destinazione. Quando rinomineremo, dovremo usarlo sul nome del file di destinazione.

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Ecco lo script completo di riconoscimento delle cartelle ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Ovviamente funziona anche quando non abbiamo cartelle di destinazione specifiche.

Se volessimo mettere tutte le canzoni in una cartella, ./Beethoven/possiamo farlo:

uso

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Round bonus ...

Utilizzando questo script per spostare i file dalle cartelle in una singola cartella:

Supponendo di voler raccogliere tutti i file corrispondenti e inserirli nella cartella corrente, possiamo farlo:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Nota sui pattern di espressioni regolari sed

In questo script si applicano le regole dei pattern sed regolari, questi pattern non sono PCRE (Perl Compatible Regular Expressions). Potresti avere sed esteso la sintassi delle espressioni regolari, usando o sed -ro a sed -E seconda della tua piattaforma.

Vedere la conformità POSIX man re_formatper una descrizione completa dei pattern regexp di base ed estesi di sed.


4

Trovo che rinominare sia uno strumento molto più semplice da usare per questo genere di cose. L'ho trovato su Homebrew per OSX

Per il tuo esempio farei:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

La 's' significa sostituto. Il modulo è s / searchPattern / replacement / files_to_apply. È necessario utilizzare regex per questo che richiede un po 'di studio ma ne vale la pena.


Sono d'accordo. Per una cosa occasionale, usare fore così sedvia va bene, ma è molto semplice e veloce da usare renamese ti ritrovi a farlo spesso.
argentum2f

Stai scappando periodi?
Big Money

Il problema è che questo non è portatile; non solo non tutte le piattaforme hanno un renamecomando; Inoltre, alcuni avranno un diverso rename comando con diverse caratteristiche, la sintassi e / o opzioni. Questo sembra il Perl renameche è abbastanza popolare; ma la risposta dovrebbe davvero chiarirlo.
tripla

3

uso migliore sedper questo, qualcosa come:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

capire l'espressione regolare è lasciato come esercizio (come si tratta di nomi di file che includono spazi)


Grazie: gli spazi non vengono utilizzati nei nomi.
Jeremy L

1

Questo sembra funzionare supponendo che

  • tutto finisce con $ pkg
  • la tua versione # inizia sempre con un "-"

togliere il .pkg, quindi rimuovere - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done

Ci sono alcuni pacchetti che hanno un trattino nel nome (vedi terzo esempio). Questo influisce sul tuo codice?
Jeremy L

1
Puoi eseguire più espressioni sed usando -e. Ad esempio sed -e 's/\.pkg//g' -e 's/-.*//g'.
martedì

Non analizzare l' lsoutput e citare le tue variabili. Vedere anche shellcheck.net per diagnosticare problemi comuni e antipattern negli script di shell.
tripleee

1

Avevo più *.txtfile da rinominare come .sqlnella stessa cartella. sotto ha funzionato per me:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done

2
Potresti migliorare la tua risposta astraendola al caso d'uso effettivo dell'OP.
dakab

Ciò presenta più problemi oltre a un apparente errore di sintassi. Per cominciare, vedi shellcheck.net .
tripleee

0

Grazie per queste risposte. Ho anche avuto qualche problema. Spostamento di file .nzb. Accodati in file .nzb. Aveva spazi e altri cruft nei nomi dei file e questo ha risolto il mio problema:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

Si basa sulla risposta di Diomidis Spinellis.

L'espressione regolare crea un gruppo per l'intero nome del file e un gruppo per la parte prima di .nzb.queued e quindi crea un comando di spostamento della shell. Con le stringhe citate. Questo evita anche di creare un ciclo nello script di shell perché questo è già fatto da sed.


Va notato che questo è vero solo per GNU sed. Su BSD sed non interpreterà l' -nopzione allo stesso modo.
ocodo
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.