用Bash或Perl重命名和移动文件

Renaming and Moving Files in Bash or Perl

嗨,我对bash和stackoverflow完全不熟悉。

我需要将一组文件(都包含在同一文件夹中)移动到目标文件夹,在该文件夹中可能已经存在同名文件。

如果存在一个特定的文件,我需要在移动之前重命名该文件,方法是在文件名后面附加一个增量整数。

扩展应该被保留(换句话说,附加的增量整数应该在扩展之前)。文件名可以包含中间的点。

最初,我想比较这两个文件夹,得到一个现有文件的列表(我用"comm"做了这个操作),但后来有点卡住了。我想我只是想用最复杂的方式做事。

有没有"bash方式"的提示?如果它是在bash脚本之外的脚本中完成的,那么就可以了。


如果您不介意重命名已经存在的文件,gnu mv具有--backup选项:

1
mv --backup=numbered * /some/other/dir


下面是一个bash脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
source="/some/dir"
dest="/another/dir"
find"$source" -maxdepth 1 -type f -printf"%f
"
| 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

后缀是盲目添加的。如果在两个目录中都有名为"foo.new"的文件,那么结果将是一个名为"foo.new"的文件和第二个名为"foo.new.new"的文件,这可能看起来很傻,但这是正确的,因为它不会覆盖文件。但是,如果目标已经包含"foo.new.new"(源和目标中都包含"foo.new"),则将覆盖"foo.new.new")。

您可以将上面的if更改为循环,以处理这种情况。此版本还保留扩展名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
source="/some/dir"
dest="/another/dir"
find"$source" -maxdepth 1 -type f -printf"%f
"
| 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


根据OP,这可以是Perl,而不仅仅是bash。我们走吧

新解决方案:(注意延伸)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
~/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: $!
";
        } }'


~/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

老办法:(不注意延伸)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
~/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: $!
";
        } }'


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


如果不需要增量后缀,rsync可以执行以下操作:

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

更新:

find/sed/sort用于管理版本化备份文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/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

我不想测试就把这个贴出来。但是已经晚了,我早上有工作。我的尝试看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
## 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

总的来说,策略是从源路径中获取每个名称,将其分解为多个部分,并且对于任何冲突,迭代形式为"base~xx.extension"的名称,直到找到一个不冲突的名称。

很明显,我已经用一个echo预先准备了mv命令,因为我是个懦夫。删除它,你自己承担风险。