English 中文(简体)
重命名多个文件和文件夹的Shell脚本
原标题:Shell script to rename multiple files and folders
  • 时间:2011-09-29 09:46:46
  •  标签:
  • 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
...

I would like to use a bash script that searches files and subdirectories name in a specific directory /home for a specific string, and when it finds the search string (in file or
name, file content, subdirectories name), replaces the string (old1) with new string (new1), and so on old2 with 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

例如,将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. create a file that specify filtering routines: 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 $   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

This above script from Shawn Chin worked perfectly replacing/renaming strings, how can I modify it if I have multiple string to be modified and these strings allocated in a file
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 $   DIR_NAME; do
cd "$DIR_NAME"           # go to target dir

# for each file/dir at this level
while IFS= read -r -d $   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;

~

最佳回答

根据我的了解,您希望遍历目录结构并执行以下操作:

  1. Replace keywords/patterns within file or directory names.
  2. Replace occurrences of the keywords/patterns within the content of files.

以下是我该怎么做。

警告:答案很长。如果你很着急,直接跳到最后一节看完整的剧本。也可能是过度工程师化(应该有一种更简单的方法来做到这一点,真的…)

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

[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)

Replacing patterns within files

这是容易的一点。只需使用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)

Recursively renaming files/dirs

我最初认为它会像<code>find一样简单-exec重命名1.2.3.4 5.6.7.8{}

不幸的是,这不起作用。用<code>echo</code>预处理命令可以说明它不起作用的原因。

[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

顶级目录(<code>./1.2.3.4</code>)的重命名将成功,但一旦我们下一级重命名<code>/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 $   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

Combining both steps into one script

我们可以通过简单地将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 $   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

Update (renaming and rewriting files based on external sed file)

假设您有一个sed文件<code>过滤器。sed</code>:

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

但是,重命名文件/目录比较麻烦(除非您使用基于Perl的rename命令仅在基于debian的系统上可用。有了这个命令,您可能可以按照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 $   DIR_NAME; do
    cd "$DIR_NAME"           # go to target dir

    # for each file/dir at this level
    while IFS= read -r -d $   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
问题回答

您可以为每个替换运行<code>rename</code>(是的,这是一个现有程序)。例如,在Bash>=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/**

我不完全理解你想要实现什么,但我认为这至少会修复你的代码:

mv "$FILE" "$(echo "$FILE" | sed -i  s/old1/new1/ ; .... ; s/oldn/newn/ )"
  • Sed works on standard input or file content, not argument text or file names or anything like that.
  • When reading output of find, do not store it all in variable, but read it line by line.

所以你想做这样的事情:

find $FILES_PATH -type f | while read FILE; do
    NEWNAME="$(printf  %s
  "$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




相关问题
Parse players currently in lobby

I m attempting to write a bash script to parse out the following log file and give me a list of CURRENT players in the room (so ignoring players that left, but including players that may have rejoined)...

encoding of file shell script

How can I check the file encoding in a shell script? I need to know if a file is encoded in utf-8 or iso-8859-1. Thanks

Bash usage of vi or emacs

From a programming standpoint, when you set the bash shell to use vi or emacs via set -o vi or set -o emacs What is actually going on here? I ve been reading a book where it claims the bash shell ...

Dynamically building a command in bash

I am construcing a command in bash dynamically. This works fine: COMMAND="java myclass" ${COMMAND} Now I want to dynamically construct a command that redirectes the output: LOG=">> myfile.log ...

Perform OR on two hash outputs of sha1sum

I want perform sha1sum file1 and sha1sum file2 and perform bitwise OR operation with them using bash. Output should be printable i.e 53a23bc2e24d039 ... (160 bit) How can I do this? I know echo $(( ...

Set screen-title from shellscript

Is it possible to set the Screen Title using a shell script? I thought about something like sending the key commands ctrl+A shift-A Name enter I searched for about an hour on how to emulate ...

热门标签