如何使用bash脚本替换文件名中的空格


264

谁能推荐一种安全的解决方案,从给定的根目录开始用文件名和目录名中的下划线递归替换空格?例如:

$ tree
.
|-- a dir
|   `-- file with spaces.txt
`-- b dir
    |-- another file with spaces.txt
    `-- yet another file with spaces.pdf

变成:

$ tree
.
|-- a_dir
|   `-- file_with_spaces.txt
`-- b_dir
    |-- another_file_with_spaces.txt
    `-- yet_another_file_with_spaces.pdf

9
如果在同一目录中有一个名为的文件foo bar,又有另一个名为的文件,您想怎么办foo_bar
Mark Byers 2010年

好问题。我不想覆盖现有文件或丢失任何数据。它应该保持不变。.理想情况下打印警告,但这可能要求太多。
armandino

Answers:


311

使用rename(aka prename)这是一个Perl脚本,它可能已经在您的系统上。分两个步骤进行:

find -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find -name "* *" -type f | rename 's/ /_/g'

基于Jürgen的回答,并且能够使用/usr/bin/rename(Perl脚本)的“ Revision 1.5 1998/12/18 16:16:31 rmb1”版本在一个绑定中处理多层文件和目录:

find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;

5
没有需要两个步骤:使用深度优先搜索:找到DIR -深度
于尔根Hötzel

3
哦,我刚读了rename手册页(我不知道该工具),我想你可以通过改变优化你的代码s/ /_/g,以y/ /_/;-)
迈克尔Krelin -黑客

1
当然,您不会从中获得性能提升。更多有关使用正确工具的信息。这整个问题是关于或多或少的微优化。毕竟好玩吗?;-)
Michael Krelin-黑客2010年

14
如果您是在OS X上运行此程序,则需要brew install rename
loeschg 2014年

7
这在Centos 7上不起作用,因为重命名命令完全不同(它是二进制文件,而不是perl脚本),并且不接受来自stdin的数据。
CpnCrunch 2015年

357

我用:

for f in *\ *; do mv "$f" "${f// /_}"; done

尽管它不是递归的,但它非常快速和简单。我确定这里有人可以将其更新为递归。

${f// /_}部分利用bash的参数扩展机制用提供的字符串替换参数中的模式。相关语法为${parameter/pattern/string}。请参阅:https : //www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.htmlhttp://wiki.bash-hackers.org/syntax/pe


9
简单,可在Mac中工作。(mac没有rename,并且很难通过brew安装。)
JonnieJS 2014年

6
很棒的答案。我用for d in *\ *; do mv "$d" "${d// /}"; done非得分。
李允(Yoon Lee)

1
与“ find -name”答案不同,此答案适用于我的OS X!谢谢你,先生!
Julio Faerman

1
作为参考,这很容易成为在bash递归使用shopt -s globstarfor f in **/*\ *; do ...。该globstar选项在bash内部,而该rename命令是常用的Linux工具,不是bash的一部分。
ghoti

2
${f// /_}是用于搜索和替换Bash变量扩展。- 是循环中每个包含空格的文件的变量。-第一个意思是“全部替换”(第一次出现时不要停止)。-然后,`/ _`装置“代替以下划线空间”ffor//
阿里

101
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done

一开始未能正确解决问题,因为我没有想到目录。


1
奇迹般有效。谢谢迈克尔。
armandino

丹尼斯(Dennis),高手,很容易被IFS=''放到前面read。另外,对于我可以通过其他注释告诉我的内容,sort可以将step -depth改为option find
Michael Krelin-黑客2010年

1
如果文件名包含\ (反斜杠),则无效。可以通过添加-r读取选项来修复。
jfg956

8
这必须是我第50次访问此页面来复制和使用您的解决方案。非常感谢你。我喜欢您的答案,因为我在Mac上,并且没有renameDennis建议的命令。
Alex Constantin

@AlexConstantin,不macports具备rename?我从不费心去寻找答案,因为我认为该任务无法证明实用性。如果没有macports,则应考虑安装它们;)
Michael Krelin-hacker 2013年


12

一个查找/重命名的解决方案。重命名是util-linux的一部分。

您需要先降低深度,因为空格文件名可以是空格目录的一部分:

find /tmp/ -depth -name "* *" -execdir rename " " "_" "{}" ";"

当我经营你的房子时,我什么都没有。
暂停,直到另行通知。

检查UTIL-Linux的安装:$ --version(UTIL-Linux的纳克2.17.2)更名更改
于尔根Hötzel

抓/ usr / bin / rename(Perl脚本)将显示“ Revision 1.5 1998/12/18 16:16:31 rmb1”
已暂停,直到另行通知。

嗯...您的util-linux二进制文件哪里去了?该文件路径应由util-linux拥有。您不是我们的GNU-Linux系统吗?
于尔根Hötzel

1
在我的跑步中只能改变一个空间,所以“在山上说出火”变成了“在山上说出火”。
brokkr 2012年

6

bash 4.0

#!/bin/bash
shopt -s globstar
for file in **/*\ *
do 
    mv "$file" "${file// /_}"       
done

如果文件或目录名称中没有空格(mv:不能移动a' to a subdirectory of itself, a / a'),这似乎将对自己进行mv处理
armandino 2010年

没关系 只需通过重定向到删除错误消息即可/dev/null
ghostdog74

鬼狗只生成mv五万五千次以重命名四个文件,即使您不向用户发送消息,也可能会产生一些开销。
Michael Krelin-黑客2010年

krelin,甚至find都将遍历您提到的55000个文件,以找到带空格的文件,然后重命名。在后端,它仍然贯穿所有。如果需要,可以在重命名之前进行初步的空格检查。
ghostdog74 2010年

我说的是产生MV,没有经过。这样for file in *' '*或某些方式做得更好吗?
Michael Krelin-黑客

6

您可以使用此:

    find . -name '* *' | while read fname 

do
        new_fname=`echo $fname | tr " " "_"`

        if [ -e $new_fname ]
        then
                echo "File $new_fname already exists. Not replacing $fname"
        else
                echo "Creating new file $new_fname to replace $fname"
                mv "$fname" $new_fname
        fi
done

3

Naidim的答案的递归版本。

find . -name "* *" | awk '{ print length, $0 }' | sort -nr -s | cut -d" " -f2- | while read f; do base=$(basename "$f"); newbase="${base// /_}"; mv "$(dirname "$f")/$(basename "$f")" "$(dirname "$f")/$newbase"; done

2

这是一个(非常冗长的)find -exec解决方案,它向stderr写入“文件已存在”警告:

function trspace() {
   declare dir name bname dname newname replace_char
   [ $# -lt 1 -o $# -gt 2 ] && { echo "usage: trspace dir char"; return 1; }
   dir="${1}"
   replace_char="${2:-_}"
   find "${dir}" -xdev -depth -name $'*[ \t\r\n\v\f]*' -exec bash -c '
      for ((i=1; i<=$#; i++)); do
         name="${@:i:1}"
         dname="${name%/*}"
         bname="${name##*/}"
         newname="${dname}/${bname//[[:space:]]/${0}}"
         if [[ -e "${newname}" ]]; then
            echo "Warning: file already exists: ${newname}" 1>&2
         else
            mv "${name}" "${newname}"
         fi
      done
  ' "${replace_char}" '{}' +
}

trspace rootdir _

2

这一点做得更多。我用它来重命名我下载的种子(没有特殊字符(非ASCII),空格,多个点等)。

#!/usr/bin/perl

&rena(`find . -type d`);
&rena(`find . -type f`);

sub rena
{
    ($elems)=@_;
    @t=split /\n/,$elems;

    for $e (@t)
    {
    $_=$e;
    # remove ./ of find
    s/^\.\///;
    # non ascii transliterate
    tr [\200-\377][_];
    tr [\000-\40][_];
    # special characters we do not want in paths
    s/[ \-\,\;\?\+\'\"\!\[\]\(\)\@\#]/_/g;
    # multiple dots except for extension
    while (/\..*\./)
    {
        s/\./_/;
    }
    # only one _ consecutive
    s/_+/_/g;
    next if ($_ eq $e ) or ("./$_" eq $e);
    print "$e -> $_\n";
    rename ($e,$_);
    }
}

1

我发现这个脚本很有趣,

 IFS=$'\n';for f in `find .`; do file=$(echo $f | tr [:blank:] '_'); [ -e $f ] && [ ! -e $file ] && mv "$f" $file;done;unset IFS

名称中带有换行符的文件失败。
ghoti


1

对于那些使用macOS苦苦挣扎的人,请首先安装所有工具:

 brew install tree findutils rename

然后,当需要重命名时,为GNU find(gfind)命名为find。然后运行@Michel Krelin的代码:

alias find=gfind 
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done   

0

这只会在当前目录中查找文件并将其重命名。我有这个别名。

find ./ -name "* *" -type f -d 1 | perl -ple '$file = $_; $file =~ s/\s+/_/g; rename($_, $file);


0

我只是出于自己的目的制作一个。您可以将其用作参考。

#!/bin/bash
cd /vzwhome/c0cheh1/dev_source/UB_14_8
for file in *
do
    echo $file
    cd "/vzwhome/c0cheh1/dev_source/UB_14_8/$file/Configuration/$file"
    echo "==> `pwd`"
    for subfile in *\ *; do [ -d "$subfile" ] && ( mv "$subfile" "$(echo $subfile | sed -e 's/ /_/g')" ); done
    ls
    cd /vzwhome/c0cheh1/dev_source/UB_14_8
done

0

对于名为/ files的文件夹中的文件

for i in `IFS="";find /files -name *\ *`
do
   echo $i
done > /tmp/list


while read line
do
   mv "$line" `echo $line | sed 's/ /_/g'`
done < /tmp/list

rm /tmp/list

0

我对这个问题的解决方案是一个bash脚本:

#!/bin/bash
directory=$1
cd "$directory"
while [ "$(find ./ -regex '.* .*' | wc -l)" -gt 0 ];
do filename="$(find ./ -regex '.* .*' | head -n 1)"
mv "$filename" "$(echo "$filename" | sed 's|'" "'|_|g')"
done

只需在执行脚本后将要在其中应用脚本的目录名称作为自变量即可。


0

在macOS中

就像选择的答案一样。

brew install rename

# 
cd <your dir>
find . -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find . -name "* *" -type f | rename 's/ /_/g'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.