Sostituisci i punti con i trattini bassi nei nomi dei file, lasciando intatta l'estensione


8

Ho uno script bash che sto cercando di sostituire i punti nei nomi dei file e sostituirli con caratteri di sottolineatura, lasciando intatta l'estensione (sono su Centos 6 btw). Come puoi vedere dall'output seguente, lo script funziona quando è presente un punto da sostituire, ma nei casi in cui l'unico punto è l'estensione, lo script tenta comunque di rinominare il file, invece di ignorarlo. Qualcuno può indicare come dovrei gestirlo meglio? Grazie per qualsiasi aiuto.

Il mio script (difettoso):

#!/bin/bash

for THISFILE in *
do
  filename=${THISFILE%\.*}
  extension=${THISFILE##*\.}
  newname=${filename//./_}
  echo "mv $THISFILE ${newname}.${extension}"
  #mv $THISFILE ${newname}.${extension}
done

Input di esempio:

1.3MN-Pin-Eurotunnel-Stw505.51.024-EGS-130x130.jpg
Wear-Plates.jpg

Produzione:

mv 1_3MN-Pin-Eurotunnel-Stw505_51_024-EGS1-130x130.jpg 1_3MN-Pin-Eurotunnel-Stw505_51_024-EGS1-130x130.jpg
mv Wear-Plates_jpg.Wear-Plates_jpg Wear-Plates_jpg.Wear-Plates_jpg

1
Che dire di casi difficili come i tar.gzfile? Vorresti che si risolvessero file.tar.gz, no file_tar.gz.
IQAndreas

Risposte:


10

Credo che questo programma farà quello che vuoi. L'ho testato e funziona su diversi casi interessanti (come nessuna estensione):

#!/bin/bash

for fname in *; do
  name="${fname%\.*}"
  extension="${fname#$name}"
  newname="${name//./_}"
  newfname="$newname""$extension"
  if [ "$fname" != "$newfname" ]; then
    echo mv "$fname" "$newfname"
    #mv "$fname" "$newfname"
  fi
done

Il problema principale che hai avuto è stato che l' ##espansione non stava facendo quello che volevi. Ho sempre considerato l'espansione dei parametri della shell in bash come una sorta di arte nera. Le spiegazioni nel manuale non sono completamente chiare e mancano di esempi di supporto su come dovrebbe funzionare l'espansione. Sono anche piuttosto criptici.

Personalmente, avrei scritto una piccola sceneggiatura sedche alternava il nome nel modo che desideravo, o una piccola sceneggiatura perlche faceva proprio tutto. Una delle altre persone che hanno risposto ha adottato questo approccio.

Un'altra cosa che vorrei sottolineare è il mio uso del preventivo. Ogni volta che faccio qualsiasi cosa con gli script di shell, ricordo alle persone di stare molto attenti con le loro citazioni. Un'enorme fonte di problemi negli script di shell è la shell che interpreta cose che non dovrebbe. E le regole di citazione sono tutt'altro che ovvie. Credo che questo script di shell sia privo di problemi di quotazione.


Questo fa davvero bene il lavoro :) Grazie anche per la spiegazione sull'espansione della shell.
bsod99,

Oltre a sostituire il punto (.) Con (_), come posso eliminare gli spazi nei nomi dei file.
discipulus,

Questa è una sceneggiatura molto bella. L'unico miglioramento che posso vedere è quello di far ricorrere ad un albero di directory e di permetterti di sostituire $ 1 con $ 2, ma quelli sono minori. (O, come diceva il mio insegnante, "lasciato come esercizio per lo studente"!)
lbutlr,

4

Usa for thisfile in *.*.*(ovvero, passa in rassegna i file con due o più punti nel loro nome). Ricorda di citare le tue variabili e utilizzare --per contrassegnare la fine delle opzioni come inmv -- "$thisfile" "$newname.$extension"

Con zsh.

autoload -U zmv
zmv '(*).(*)' '${1//./_}.$2'

Potrei aver implementato il tuo suggerimento erroneamente, ma questo si traduce in mv - . . * _ . *
bsod99,

3

Cosa ne pensi di questo:

perl -e '
         @files = grep {-f} glob "*";
         @old_files = @files;
         map {
              s!(.*)\.!$1/!;
              s!\.!_!g;
              s!/!.!
             } @files;
         rename $old_files[$_] => $files[$_] for (0..$#files)
        '

NOTA BENE: provalo prima su una directory fittizia, non l'ho provato!


Terse, pithy, quasi solo in scrittura! Yay perl! ridacchiano
Onnipotente il

Sola scrittura? Scommetto che è la prima volta che Perl viene chiamato così! ( ridacchia ancora )
Joseph R.,

Ci ho provato a migliorare la leggibilità lì. Spero che questo sembri meno "di sola scrittura" :)
Joseph R.,

1
Sì, questo aiuta un sacco. È un peccato che l'unico personaggio ragionevole da usare come segnaposto ci sia /.
Onnipotente il

2

Sembra che alcune buone risposte siano già disponibili, ma eccone un'altra che utilizza tre sed:

#!/bin/bash

for file in *; do
    newname=$(echo $file | tr '.' '_' | sed 's/\(.*\)_\([^_]*\)$/\1.\2/g')
    [ "$newname" != "$file" ] && mv "$file" "$newname"
done

Come seddecide a quale .*applicare il munch massimo? Mi sento molto meglio su di esso se la seconda .*fosse [^_]*.
Onnipotente il

Credo anche che questo si ripercuota sul caso di "nessuna estensione" e di un nome file con \tcaratteri.
Onnipotente il

Questa è stata sicuramente solo una soluzione rapida. Grazie per il tuo contributo ... Ho risolto il problema con regex per soddisfare la tua prima raccomandazione. Vedrò se posso modificarlo per tenere conto delle altre circostanze che hai menzionato.
JC Yamokoski il

echo -n "$file"funzionerebbe. :-) Oh, ed "è attorno $( ... )all'espressione (aka "$( ... )") che lo farebbe.
Onnipotente il

1

Questa versione consente di selezionare esplicitamente il numero di punti che si desidera conservare, a partire dal lato destro.

Inoltre sostituirà e / o eliminerà altri caratteri oltre ai punti e il carattere sostitutivo è -invece di un trattino basso, ma questo può essere facilmente modificato.

#!/bin/sh
# Rename files by replacing Unix-unfriendly characters.

usage () {
    cat <<EOF
usage: $0 [OPTIONS] [--] [FILE [FILE...]]
Rename files by replacing Unix-unfriendly characters.

Options:
 -p N              preserve last N dots in filename, or keep all
                   dots if N < 0 (default: 1)
       --help      show this help and exit
EOF
}

error () {
    printf "%s\n" "$1" 1>&2
}

delete_chars="()[]{}*?!^~%\\\<>&\$#|'\`\""
replace_chars=" _.,;-"

unixify_string () (
    printf '%s\n' "$1" \
        | tr -d "$delete_chars" \
        | tr -s "$replace_chars" - \
        | to_lower \
        | sed 's/^-\(.\)/\1/; s/\(.\)-$/\1/'
)

to_lower () {
    sed 's/.*/\L&/'
}

split () (
    # split '.x.x.x.x'  0 -> '/x.x.x.x.x
    # split '.x.x.x.x'  1 -> '/x.x.x.x/x
    # split '.x.x.x.x'  2 -> '/x.x.x/x/x
    # split '.x.x.x.x' -1 -> '/x/x/x/x/x
    nf=$(printf '%s\n' "$1" | tr -d -C . | wc -c)
    if [ $2 -lt 0 ]; then
        keep=0
    else
        keep=$((nf-$2))
    fi
    IFS=. i=0 out= sep=
    for part in $1; do
        out="$out$sep$part"
        if [ -z "$out" -o $i -ge $keep ]; then
            sep=/
        else
            sep=.
        fi
        i=$(($i+1))
    done
    printf '%s\n' "$out"
)

unixify () (
    IFS=/ out= sep=
    for part in $(split "$1" $2); do
        out="$out$sep$(unixify_string "$part")"
        sep=.
    done
    printf '%s\n' "$out"
)

rename_maybe () (
    dir="$(dirname "$1")"
    name="$(basename "$1")"
    newname="$(unixify "$name" $2)"
    if [ "$newname" != "$name" ]; then
        mv -i "$dir/$name" "$dir/$newname"
    fi
)

# command line arguments

short_opts=p:
long_opts=help

args="$(LC_ALL=C getopt -n "$0" -s sh -o $short_opts -l $long_opts -- "$@")"
if [ $? -eq 0 ]; then
    eval set -- "$args"
else
    exit 1
fi

p=
while [ $# -gt 0 ]; do
    case "$1" in
        --help)
            usage; exit 0 ;;
        -p)
            p="$2"; shift
            if ! [ "$p" -eq "$p" ] 2> /dev/null; then
                error "$0: option requires integer argument -- 'p'"
                exit 1
            fi ;;
        --)
            shift; break ;;
        -*)
            error "$0: illegal option -- '$1'"
            exit 1 ;;
        *)
            break
    esac
    shift
done

# defaults
p=${p:-1}

# echo p=$p
# echo "$@"
# echo n=$#
# exit

if [ $# -lt 1 ]; then
    error "$0: required non-option argument missing"
    exit 1
fi

for file in "$@"; do
    rename_maybe "$file" $p
done
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.