用于重命名多个文件和文件夹的Shell脚本

时间:2011-09-29 09:46:47

标签: bash

我有以下结构

-1.2.3.4
--1.2.3.4
---1.2.3.4-->1.2.3.4.file

-5.6.7.8
--5.6.7.8
---5.6.7.8-->5.6.7.8.file
...

我想使用bash脚本在特定目录/ home中搜索特定字符串的文件和子目录名称,并在找到搜索字符串时(在文件或中) name,file content,subdirectories name),将字符串(old1)替换为new string(new1),将old2替换为new2 .... oldn with newn。

#!/bin/bash
FILES_PATH="/home/pons/test"
FILES=$(find $FILES_PATH -type f -name "*")
for FILE in $FILES; do
    sed -i 's/old1/new1/ ; .... ; s/oldn/newn/' $FILE
done

e.g。在所有

中将fadi替换为服务器
[pons@server1 ~]$ pwd 
/home/pons 
[pons@server1 ~]$ cd test
[pons@server1 test]$ ls -al
total 12

drwxrwxr-x   3 pons pons 4096 Sep 29 09:50 .
drwx------. 26 pons pons 4096 Sep 29 10:21 ..
drwxrwxr-x   2 pons pons 4096 Sep 29 09:47 fadi
[pons@server1 test]$ cd fadi/ 
[pons@server1 fadi]$ pwd 
/home/pons/test/fadi
[pons@server1 fadi]$ ls -al
total 12<br/>
drwxrwxr-x 2 pons pons 4096 Sep 29 09:47 .
drwxrwxr-x 3 pons pons 4096 Sep 29 09:50 ..
-rw-rw-r-- 1 pons pons   27 Sep 29 09:47 xxxfadixx.txt
[pons@server1 fadi]$ cat xxxfadixx.txt 
xxxxxxxxxxxxfadixxxxxxxxxx
[pons@server1 fadi]$ 

我可以在一个文件中添加多个字符串并执行替换

e.g。创建一个指定过滤例程的文件:     filter.txt
    S / old1 /名new1 /
    S / old2 / NEW2 /
    S / old3 / NEW3 /
    ......     S / oldn / newn /

然后在脚本中读取该文件

#!/bin/bash

ROOT_DIR="$HOME/root"  # your target dir
P_FROM="1.2.3.4"
P_TO="5.6.7.8"

# for each directory, starting from deepest first
while IFS= read -r -d $'\0' DIR_NAME; do
cd "$DIR_NAME"           # go to target dir
rename $P_FROM $P_TO *   # rename files
find . -type f -maxdepth 1 -exec sed -i "s/$P_FROM/$P_TO/g" {} \;  
cd - > /dev/null         # back to original dir. Suppress stdout
done < <(find $ROOT_DIR -type d -depth -print0) # get only dirs

Shawn Chin的上述脚本完美地替换/重命名字符串,如果我要修改多个字符串并将这些字符串分配到文件中,如何修改它  filter.txt
    s / old1 / new1 /
    s / old2 / new2 /
    S / old3 / NEW3 /
    ......     s / oldn / newn /

#!/bin/bash
ROOT_DIR="$HOME/test"  # your target dir
FILTER_FILE="$HOME/filter.sed"  # the sed script for renaming

# custom rename function that uses $FILTER_FILE (via sed)
function rename_using_filter {
CURRENT_NAME="$1"
NEW_NAME="$(echo $1 | sed -f $FILTER_FILE)"  # derive new name
if [ "$CURRENT_NAME" != "$NEW_NAME" ]; then  # rename if diff
    mv "$CURRENT_NAME" "$NEW_NAME"
fi
}

# for each directory, starting from deepest first
while IFS= read -r -d $'\0' DIR_NAME; do
cd "$DIR_NAME"           # go to target dir

# for each file/dir at this level
while IFS= read -r -d $'\0' FILE_NAME; do
    rename_using_filter "$FILE_NAME"  # rename it
    if [ -f "$FILE_NAME" ]; then                # if it's a file
        sed -i -f "$FILTER_FILE" "$FILE_NAME";  # replace content
    fi
done < <(find . -maxdepth 1 -print0)

cd - > /dev/null         # back to original dir. Suppress stdout
done < <(find $ROOT_DIR -depth -type d -print0) # get only dirs

我使用filter.sed完成了上述操作,它可以替换名称文件/目录,但没有替换文件的内容......请你检查一下。

[pons@server1 test]$ find .
.
./boy_dir
./boy_dir/boy.txt
./papa
./papa/papa_new
./papa/papa_new/papa.txt
./boy
[pons@server1 test]$ cat ./papa/papa_new/papa.txt
xxxxxxxxxxxxxxxxxxxxpapaxxxxxxxxxxxx
[pons@server1 test]$ cat ./boy_dir/boy.txt
xxxxxxxxxxxxboyxxxxxxxxx
[pons@server1 test]$ cd ..
[pons@server1 ~]$ ./replace.sh 
[pons@server1 ~]$ cd test
[pons@server1 test]$ find .
.
./girl_dir
./girl_dir/girl.txt
./girl
./mama
./mama/mama_new
./mama/mama_new/mama.txt
[pons@server1 test]$ cat ./mama/mama_new/mama.txt
xxxxxxxxxxxxxxxxxxxxpapaxxxxxxxxxxxx
[pons@server1 test]$ cat ./girl_dir/girl.txt
xxxxxxxxxxxxboyxxxxxxxxx
[pons@server1 test]$ 


I used this filter 
[pons@server1 ~]$ cat filter.sed 
s/papa/mama/g;
s/boy/girl/g;
s/old3/new3/g;
s/old3/new4/g;
s/oldn/newn/g;




"filter.sed" 5L, 95C written                                                                                      
[pons@server1 ~]$ ./replace.sh 
[pons@server1 ~]$ cd test
[pons@server1 test]$ find .
.
./A.B.C.D.E_
./a.b.c.d.e
./a.b.c.d.e/a.b.c.d.e
./a.b.c.d.e_
./A.B.C.D.E
./A.B.C.D.E/A.B.C.D.E
[pons@server1 test]$ cat ./A.B.C.D.E/A.B.C.D.E
1.2.3.4.5
[pons@server1 test]$ cat ./a.b.c.d.e/a.b.c.d.e
6.7.8.9.0
[pons@server1 test]$ cat ./A.B.C.D.E_
1.2.3.4.5
[pons@server1 test]$ cat ./a.b.c.d.e_
6.7.8.9.0
[pons@server1 test]$ cd ..
[pons@server1 ~]$ ./replace.sh 
[pons@server1 ~]$ cd test
[pons@server1 test]$ find .
.
./A.B.C.D.E_
./a.b.c.d.e
./a.b.c.d.e/a.b.c.d.e
./a.b.c.d.e_
./A.B.C.D.E
./A.B.C.D.E/A.B.C.D.E
[pons@server1 test]$ cat ./A.B.C.D.E/A.B.C.D.E
A.B.C.D.E
[pons@server1 test]$ cat ./a.b.c.d.e/a.b.c.d.e
a.b.c.d.e
[pons@server1 test]$ cat ./A.B.C.D.E_
A.B.C.D.E
[pons@server1 test]$ cat ./a.b.c.d.e_
a.b.c.d.e
[pons@server1 test]$


[pons@server1 ~]$ vi filter.sed 

s/1.2.3.4.5/A.B.C.D.E/g;
s/6.7.8.9.0/a.b.c.d.e/g;
s/old3/new3/g;
s/old3/new4/g;
s/oldn/newn/g;

4 个答案:

答案 0 :(得分:3)

您可以为每次替换运行rename(是的,这是现有程序)。例如,在Bash&gt; = 4.0中,这里是如何从当前目录中的所有文件中删除“.bak”扩展名:

shopt -s globstar # Enable recursive globs with **
rename 's/\.bak$//' **.bak

另一个例子,做几次替换:

$ touch /tmp/foobarbaz
$ rename 's/foo/bar/g;s/bar/baz/g' /tmp/foobarbaz
$ ls /tmp
bazbazbaz

对于您的特定替换,这应该有效:

rename 's/old1/new1/ ; .... ; s/oldn/newn/' /home/pons/test/**

答案 1 :(得分:3)

从我收集的内容中,您想要遍历目录结构并执行以下操作:

  1. 替换文件或目录名称中的关键字/模式。
  2. 替换文件内容中出现的关键字/模式。
  3. 我将如何做到这一点。

    警告:答案很长。如果您赶时间,请直接跳到最后一节以获取完整的脚本。也可能是过度工程师(应该有一种更简单的方法来实现这一点,真的......)。

    假设以下文件结构(基于您的第一个示例):

    [me@home]$ find .
    .
    ./1.2.3.4
    ./1.2.3.4/1.2.3.4
    ./1.2.3.4/1.2.3.4/1.2.3.4
    ./1.2.3.4/1.2.3.4/1.2.3.4/1.2.3.4.file
    ./1.2.3.4/1.2.3.4/1.2.3.4/1.2.3.4-2.file
    
    
    [me@home]$ cat ./1.2.3.4/1.2.3.4/1.2.3.4/1.2.3.4.file
    IP: 1.2.3.4
    
    [me@home]$ cat ./1.2.3.4/1.2.3.4/1.2.3.4/1.2.3.4-2.file
    IP: 1.2.3.4 (also)
    

    替换文件中的模式

    这很容易。只需使用findsed

    ,就可以做到这一点
    [me@home]$ find . -type f -exec sed -i "s/1\.2\.3\.4/5.6.7.8/g" {} \;
    
    [me@home]$ cat ./1.2.3.4/1.2.3.4/1.2.3.4/1.2.3.4.file
    IP: 5.6.7.8
    
    [me@home]$ cat ./1.2.3.4/1.2.3.4/1.2.3.4/1.2.3.4-2.file
    IP: 5.6.7.8 (also)
    

    递归重命名文件/目录

    我首先假设它会像find . -exec rename 1.2.3.4 5.6.7.8 {} \;一样简单。

    不幸的是,这不起作用。使用echo添加命令会显示无法使用的原因。

    [me@home]$ find . -exec echo rename 1.2.3.4 5.6.7.8 {} \;
    rename 1.2.3.4 5.6.7.8 .
    rename 1.2.3.4 5.6.7.8 ./1.2.3.4
    rename 1.2.3.4 5.6.7.8 ./1.2.3.4/1.2.3.4
    rename 1.2.3.4 5.6.7.8 ./1.2.3.4/1.2.3.4/1.2.3.4
    rename 1.2.3.4 5.6.7.8 ./1.2.3.4/1.2.3.4/1.2.3.4/1.2.3.4.file
    rename 1.2.3.4 5.6.7.8 ./1.2.3.4/1.2.3.4/1.2.3.4/1.2.3.4-2.file
    

    顶级目录(./1.2.3.4)的重命名将成功,但是一旦我们下一级重命名./1.2.3.4/1.2.3.4,这将失败,因为父目录已经被重命名。

    我们可以颠倒遍历的顺序(使用-depth),但这只能解决问题的一半,因为我们需要每个步骤只重命名最低级别的匹配而不是整个路径。

    我们必须做的是从最低级别开始重命名过程并向上工作。这是一种方法:

    #!/bin/bash
    
    ROOT_DIR="$HOME/root"  # your target dir
    P_FROM="1.2.3.4"
    P_TO="5.6.7.8"
    
    # for each directory, starting from deepest first
    while IFS= read -r -d $'\0' DIR_NAME; do
        (cd "$DIR_NAME" && rename $P_FROM $P_TO *)
    done < <(find $ROOT_DIR -depth -type d -print0) # get only dirs
    

    外观复杂的while循环允许我们处理包含空格的文件名(有关详细信息,请参阅http://mywiki.wooledge.org/BashFAQ/020)。

    此脚本假定存在rename命令。

    在我们的测试目录上运行该脚本会产生以下结果:

    [me@home]$ bash renamer.sh
    
    [me@home]$ find .
    .
    ./5.6.7.8
    ./5.6.7.8/5.6.7.8
    ./5.6.7.8/5.6.7.8/5.6.7.8
    ./5.6.7.8/5.6.7.8/5.6.7.8/5.6.7.8.file
    ./5.6.7.8/5.6.7.8/5.6.7.8/5.6.7.8-2.file
    

    将两个步骤合并为一个脚本

    我们可以通过简单地将find + sed调用添加到循环中,将上述两个步骤合并为一个解决方案。

    #!/bin/bash
    
    ROOT_DIR="$HOME/root"  # your target dir
    P_FROM="1.2.3.4"
    P_TO="5.6.7.8"
    
    # for each directory, starting from deepest first
    while IFS= read -r -d $'\0' DIR_NAME; do
        cd "$DIR_NAME"           # go to target dir
        rename $P_FROM $P_TO *   # rename files
        find . -type f -maxdepth 1 -exec sed -i "s/$P_FROM/$P_TO/g" {} \;  
        cd - > /dev/null         # back to original dir. Suppress stdout
    done < <(find $ROOT_DIR -depth -type d -print0) # get only dirs
    

    更新(根据外部sed文件重命名和重写文件)

    假设您有一个sed文件filter.sed

    s/old1/new1/g;
    s/old2/new2/g;
    s/old3/new3/g;
    s/old3/new4/g;
    s/oldn/newn/g;
    

    只要filter.sed是有效的sed文件,替换文件的内容就很简单。我们只使用-f选项:

    [me@home]$ cat somefile.txt 
    hello old1 old2 old3 world
    
    [me@home]$ sed -i -f filter.sed somefile.txt 
    
    [me@home]$ cat somefile.txt 
    hello new1 new2 new3 world
    

    然而,重命名文件/目录却比较棘手(除非你使用仅在基于debian的系统上可用的Perl-based rename命令。这样你就可以按照rename "$(cat filter.sed)" *的方式做一些事情。

    仅使用标准mv命令,这是一种方法。我怀疑这可能不是最有效的方法,但经过简单测试后,似乎工作。 YMMV。

    #!/bin/bash
    ROOT_DIR="$HOME/root"  # your target dir
    FILTER_FILE="$HOME/filter.sed"  # the sed script for renaming
    
    # custom rename function that uses $FILTER_FILE (via sed)
    function rename_using_filter {
        CURRENT_NAME="$1"
        NEW_NAME="$(echo $1 | sed -f $FILTER_FILE)"  # derive new name
        if [ "$CURRENT_NAME" != "$NEW_NAME" ]; then  # rename if diff
            mv "$CURRENT_NAME" "$NEW_NAME"
        fi
    }
    
    # for each directory, starting from deepest first
    while IFS= read -r -d $'\0' DIR_NAME; do
        cd "$DIR_NAME"           # go to target dir
    
        # for each file/dir at this level
        while IFS= read -r -d $'\0' FILE_NAME; do
            if [ -f "$FILE_NAME" ]; then                # if it's a file
                sed -i -f "$FILTER_FILE" "$FILE_NAME"   # replace content
            fi
            rename_using_filter "$FILE_NAME"  # rename it 
        done < <(find . -maxdepth 1 -print0)
    
        cd - > /dev/null         # back to original dir. Suppress stdout
    done < <(find $ROOT_DIR -depth -type d -print0) # get only dirs
    

    以下是正在运行的脚本:

    [me@home]$ find .
    .
    ./old1_old2
    ./old1_old2/old3_old4
    ./old1_old2/old3_old4/some-oldn.txt
    ./renamr.sh
    
    [me@home]$ cat old1_old2/old3_old4/some-oldn.txt 
    hello old1 old2 old3 old4 oldn world
    
    [me@home]$ bash renamr.sh
    
    [me@home]$ find .
    .
    ./.renamr.sh.swp
    ./new1_new2
    ./new1_new2/new3_new4
    ./new1_new2/new3_new4/some-newn.txt
    ./renamr.sh
    
    [me@home]$ cat ./new1_new2/new3_new4/some-newn.txt
    hello new1 new2 new3 new4 newn world
    

答案 2 :(得分:0)

我并不完全明白你想要达到的目标,但我认为这至少会修复你的代码:

mv "$FILE" "$(echo "$FILE" | sed -i 's/old1/new1/ ; .... ; s/oldn/newn/')"

答案 3 :(得分:0)

  • Sed适用于标准输入或文件内容,而不是参数文本或文件名或类似内容。
  • 当读取find的输出时,不要将它全部存储在变量中,而是逐行读取。

所以你想做类似的事情:

find $FILES_PATH -type f | while read FILE; do
    NEWNAME="$(printf '%s\n' "$FILE" | sed 's/old1/new1/ ; .... ; s/oldn/newn/')"
    mv "$FILE" "$NEWNAME"
done

注意,对于sed,想要-i,stdin无法就地修改。

通过使用sed输出两个名称可以提高效率,因此您可以在整个输出上只运行一次,但需要使用保留空间的多个替换。它就像:

find $FILES_PATH -type f | sed 'h;s/old1/new1/;...;s/oldn/newn/;x;G' | while read OLD && read NEW; do
    mv "$OLD" "$NEW"
done