2010-03-30 5 views
5

HI, Je suis complètement nouveau sur Bash et StackOverflow.Renommer et déplacer des fichiers en Bash ou en Perl

Je dois déplacer un ensemble de fichiers (tous contenus dans le même dossier) dans un dossier cible où des fichiers portant le même nom peuvent déjà exister.

Dans le cas où un fichier spécifique existe, j'ai besoin de renommer le fichier avant de le déplacer, en ajoutant par exemple un entier incrémental au nom du fichier.

Les extensions doivent être conservées (en d'autres termes, l'entier incrémental ajouté doit aller avant l'extension). Les noms de fichier peuvent contenir des points au milieu. À l'origine, je pensais à comparer les deux dossiers pour avoir une liste des fichiers existants (j'ai fait cela avec "comm"), mais ensuite je me suis un peu coincé. Je pense que j'essaie juste de faire les choses de la manière la plus compliquée possible.

Un conseil pour le faire dans le "bash"? C'est OK si cela est fait dans un script autre que le script bash.

+0

Bienvenue sur SO! :) –

+0

@Katie - J'ai pris la liberté d'éditer votre question et de mettre les deux choses qui ont été clarifiées par vous dans les commentaires. – DVK

Répondre

3

Comme par OP, cela peut être Perl, pas seulement bash. Ici, nous allons

NOUVELLE SOLUTION: (en accordant une attention à l'extension)

~/junk/a1$ ls 
f1.txt f2.txt f3.txt z1  z2 


~/junk/a1$ ls ../a2 
f1.txt  f2.1.txt f2.2.txt f2.3.txt f2.txt  z1 

# I split the one-liner into multiple lines for readability 
$ perl5.8 -e 
    '{use strict; use warnings; use File::Copy; use File::Basename; 
     my @files = glob("*"); # assume current directory 
     foreach my $file (@files) { 
      my $file_base2 = basename($file); 
      my ($file_base, $ext) = ($file_base2 =~ /(.+?)([.][^.]+$)?$/); 
      my $new_file_base = "../a2/$file_base"; 
      my $new_file = $new_file_base . $ext; 
      my $counter = 1; 
      while (-e $new_file) { 
       $new_file = "$new_file_base." . $counter++ . $ext; 
      } 
      copy($file, $new_file) 
       || die "could not copy $file to $new_file: $!\n"; 
     } }' 

~/junk/a1> ls ../a2 
f1.1.txt f1.txt f2.1.txt f2.2.txt f2.3.txt f2.4.txt f2.txt f3.txt 
z1   z1.1  z2 

SOLUTION VIEUX: (ne pas prêter attention à l'extension)

~/junk/a1$ ls 
f1 f2 f3 

~/junk/a1$ ls ../a2 
f1  f2  f2.1 f2.2 f2.3 

# I split the one-liner into multiple lines for readability 
$ perl5.8 -e 
    '{use strict; use warnings; use File::Copy; use File::Basename; 
     my @files = glob("*"); # assume current directory 
     foreach my $file (@files) { 
      my $file_base = basename($file); 
      my $new_file_base = "../a2/$file_base"; 
      my $new_file = $new_file_base; 
      my $counter = 1; 
      while (-e $new_file) { $new_file = "$new_file_base." . $counter++; } 
      copy($file,$new_file) 
       || die "could not copy $file to $new_file: $!\n"; 
     } }' 

~/junk/a1> ls ../a2 
f1  f1.1 f2  f2.1 f2.2 f2.3 f2.4 f3 
+0

C'est génial (je me fous de perl ou bash), mais je devrais appliquer le nouveau nom en conservant l'extension (obtenir quelque chose comme foo_1.txt pour foo.txt, au lieu de foo.text.1). J'essaie de comprendre comment faire cela ... – Katie

+0

@Katie - Voir les mises à jour – DVK

+0

@Katie - et une bonne attitude à propos de "ne pas se soucier tant qu'il fait le travail" :) – DVK

7

Si vous ne me dérange pas de renommer les fichiers qui existent déjà, GNU mv a l'option --backup:

mv --backup=numbered * /some/other/dir 
+0

En fait, je dois conserver les noms des fichiers du répertoire cible. De plus, je devrais préserver les extensions. Mais merci pour cet indice, il pourrait être utile dans d'autres situations ... – Katie

4

Voici un script Bash:

source="/some/dir" 
dest="/another/dir" 
find "$source" -maxdepth 1 -type f -printf "%f\n" | while read -r file 
do 
    suffix= 
    if [[ -a "$dest/$file" ]] 
    then 
     suffix=".new" 
    fi 
    # to make active, comment out the next line and uncomment the line below it 
    echo 'mv' "\"$source/$file\"" "\"$dest/$file$suffix\"" 
    # mv "source/$file" "$dest/$file$suffix" 
done 

Le suffixe est ajouté à l'aveugle. Si vous avez des fichiers nommés comme "foo.new" dans les deux répertoires, le résultat sera un fichier nommé "foo.new" et le second nommé "foo.new.new" qui peut sembler stupide, mais est correct dans le fait qu'il doesn n'écrasez pas le fichier. Cependant, si la destination contient déjà "foo.new.new" (et "foo.new" est à la fois source et destination), alors "foo.new.new" sera écrasé).

Vous pouvez modifier le if ci-dessus en boucle pour gérer cette situation. Cette version également préserve les extensions:

source="/some/dir" 
dest="/another/dir" 
find "$source" -maxdepth 1 -type f -printf "%f\n" | while read -r file 
do 
    suffix= 
    count= 
    ext= 
    base="${file%.*}" 
    if [[ $file =~ \. ]] 
    then 
     ext=".${file##*.}" 
    fi 
    while [[ -a "$dest/$base$suffix$count$ext" ]] 
    do 
     ((count+=1)) 
     suffix="." 
    done 
    # to make active, comment out the next line and uncomment the line below it 
    echo 'mv' "\"$source/$file\"" "\"$dest/$file$suffix$count$ext\"" 
    # mv "$source/$file" "$dest/$file$suffix$count$ext" 
done 
+0

@Dennis - belle touche re: commentant 'mv' :) – DVK

0

Je me sens mal pour ce détachement sans le tester. Cependant, il est tard et j'ai du travail le matin. Ma tentative ressemblerait à quelque chose comme ceci:

## copy files from src to dst  
## inserting ~XX into any name between base and extension 
## where a name collision would occur 
src="$1" 
dst="$2" 

case "$dst" in 
    /*) :;;    # absolute dest is fine 
    *) dst=$(pwd)/$dst;; # relative needs to be fixed up 
    esac 

cd "$src" 
find . -type f | while read x; do 
    x=${x#./}   # trim off the ./ 
    t=$x;    # initial target 
    d=$(dirname $x); # relative directory 
    b=$(basename $x); # initial basename 
    ext=${b%%.*};  # extension 
    b=${b##*.};   # basename with ext. stripped off 
    let zz=0;   # initial numeric 
    while [ -e "$dst/$t" ]; do 
     # target exists, so try constructing a new target name 
     t="$d/$bb~$zz.$ext" 
     let zz+=1; 
    done 
    echo mv "./$x" "$dst/$t" 
done 

Dans l'ensemble de la stratégie est d'obtenir chaque nom du chemin source, briser en plusieurs parties, et, pour toute collision, itérer sur les noms de la forme « base ~ XX. extension "jusqu'à ce que nous en trouvons un qui ne se heurte pas.

Évidemment, j'ai ajouté la commande mv avec un echo parce que je suis un lâche. Supprimez cela à votre propre péril (de fichiers).

0

Si vous ne avez pas besoin suffixe supplémentaire, rsync peut faire le travail:

rsync --archive --backup --suffix=.sic src/ dst 

Mise à jour:

find/sed/trier permet de gérer les fichiers de sauvegarde versionnés:

#!/bin/bash                           

src="${1}" 
dst="${2}" 

if test ! -d "${src}" -o ! -d "${dst}" ;then 
    echo Usage: $0 SRC_DIR DST_DIR >&2 
    exit 1 
fi 

rsync --archive --backup "${src}/" "${dst}/" 
new_name() { 
    local dst=$1 
    local prefix=$2 
    local suffix=$3 
    local max=$(find ${dst} -type f -regex ".*${prefix}.[0-9]*.${suffix}\$" \ 
     | sed 's/.*\.\([0-9]*\)\..*/\1/'|sort -n|tail -n 1) 
    let max++ 
    echo ${prefix}.${max}.${suffix} 
} 

# swap BACKUP-extension/real-extension                    
for backup_file in $(find $dst -name "*~"); do 
    file=${backup_file%~} 
    prefix=${file%.*} 
    suffix=${file##*.} 
    suffix=${suffix%\~} 
    mv ${backup_file} $(new_name $dst $prefix $suffix) 
done 
Questions connexes