Rinominare in batch i file con Bash
-
03-07-2019 - |
Domanda
Come può Bash rinominare una serie di pacchetti per rimuovere i loro numeri di versione? Ho giocato con expr
e %%
, senza risultati.
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
Soluzione
Puoi 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.
Altri suggerimenti
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 sarà quindi possibile reindirizzare nella shell. È meglio eseguire prima la pipeline senza il | finale sh
per verificare che il comando faccia quello che vuoi.
Per ricorrere attraverso più directory usa qualcosa come
find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh
Nota che in sed la sequenza di raggruppamento delle espressioni regolari sono parentesi precedute da una barra rovesciata, \ (
e \)
, anziché parentesi singole (
e )
.
Farò qualcosa del genere:
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 find come consigliato sopra da Javier.
MODIFICA : inoltre, questa versione non utilizza alcuna funzionalità specifica di bash, come altre sopra, che ti porta a una maggiore portabilità.
Ecco un quasi equivalente POSIX della risposta attualmente accettata . Questo scambia l'espansione del parametro $ {variabile / sottostringa / sostituzione}
solo per Bash con uno che è disponibile in qualsiasi shell compatibile con Bourne.
for i in ./*.pkg; do
mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done
L'espansione dei parametri $ {variabile% pattern}
produce il valore di variabile
con qualsiasi suffisso che corrisponde a pattern
rimosso. (C'è anche $ {variabile # modello}
per rimuovere un prefisso.)
Ho tenuto il sottotatro - [0-9.] *
dalla risposta accettata sebbene sia 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 "tutto" sarà la partita più breve possibile, non la più lunga. (Bash offre ##
e %%
per tagliare il prefisso o il suffisso più lungo possibile, piuttosto che il più corto.)
usare meglio sed
per 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 un esercizio (come si tratta di nomi di file che includono spazi)
Possiamo supporre che sed
sia disponibile su qualsiasi * nix, ma non possiamo esserne sicuri
supporterà sed -n
per generare comandi mv. ( NOTA: solo GNU sed
fa questo.)
Anche così, bash builtin e sed, possiamo rapidamente attivare una funzione shell per fare questo.
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: 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
sed_pattern files..."
fi
}
Uso
abspath () { case "$1" in
/*)printf "%s\n" "$1";;
*)printf "%s\n" "$PWD/$1";;
esac; }
Creazione di cartelle di destinazione:
Poiché mv
non crea automaticamente cartelle target che non possiamo usare
la nostra versione iniziale di sedrename
.
È una modifica abbastanza piccola, quindi sarebbe bello includere quella funzione:
Avremo bisogno di una funzione di utilità, abspath
(o percorso assoluto) da bash
non ha questo build in.
# 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"
Una volta che abbiamo che possiamo generare le cartelle di destinazione per a modello sed / rename che include la nuova struttura di cartelle.
Questo ci assicurerà di conoscere i nomi delle nostre cartelle di destinazione. Quando noi rinominare avremo bisogno di usarlo sul nome del file di destinazione.
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: 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
sed_pattern files..."
fi
}
Ecco lo script compatibile con la cartella completa ...
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
Ovviamente funziona ancora quando non abbiamo cartelle di destinazione specifiche anche.
Se volessimo mettere tutti i brani in una cartella, ./Beethoven/
possiamo farlo:
Uso
<*>Round bonus ...
Usando questo script per spostare i file dalle cartelle in una singola cartella:
Supponendo di voler raccogliere tutti i file corrispondenti e posizionarli nella cartella corrente, possiamo farlo:
<*>Nota sui modelli sed regex
In questo script si applicano le normali regole del modello sed, questi modelli no
PCRE (espressioni regolari compatibili Perl). Potresti avere sed
sintassi estesa delle espressioni regolari, usando sed -r
o sed -E
a seconda della tua piattaforma.
Vedi man re_format
conforme a POSIX per una descrizione completa di
sed modelli di regexp di base ed estesi.
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 / sostituzione / files_to_apply. Devi usare regex per questo che richiede un po 'di studio ma ne vale la pena.
Questo sembra funzionare supponendo che
- tutto termina con $ pkg
- la tua versione # inizia sempre con un " - "
rimuovi il .pkg, quindi rimuovi - ..
for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
Avevo più file * .txt
da rinominare come .sql
nella stessa cartella.
sotto ha funzionato per me:
for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
Grazie per questa risposta. Ho anche avuto una sorta di problema. Spostamento di file .nzb.queued in file .nzb. Aveva spazi e altre 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.
Il regex crea un gruppo per l'intero nome 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 della shell perché questo è già fatto da sed.