【问题标题】:How to replace spaces in file names using a bash script如何使用 bash 脚本替换文件名中的空格
【发布时间】:2011-02-12 03:50:44
【问题描述】:

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

$ 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

【问题讨论】:

  • 如果在同一目录中有一个名为foo bar 的文件和另一个名为foo_bar 的文件,你想发生什么?
  • 好问题。我不想覆盖现有文件或丢失任何数据。它应该保持不变..理想情况下打印一个警告,但这可能要求太多了。

标签: linux bash whitespace filenames


【解决方案1】:

使用以下命令将文件名和目录名中的空格替换为下划线。

find -name "* *" -print0 | sort -rz | \
  while read -d $'\0' f; do mv -v "$f" "$(dirname "$f")/$(basename "${f// /_}")"; done

【讨论】:

    【解决方案2】:

    使用rename(又名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 的“Revision 1.5 1998/12/18 16:16:31 rmb1”版本在单个绑定中处理多层文件和目录(Perl 脚本):

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

    【讨论】:

    • 无需两步:使用深度优先搜索:find dir -depth
    • 哦,我刚刚阅读了rename 手册页(我不知道该工具),我认为您可以通过将s/ /_/g 更改为y/ /_/ 来优化您的代码;-)
    • 如果你在 OS X 上运行它,你需要brew install rename
    • 这在 Centos 7 上不起作用,因为重命名命令完全不同(它是二进制文件,不是 perl 脚本),并且它不接受来自标准输入的数据。
    • @CpnCrunch 在 RHEL 6.2 和 Cygwin 中相同(rename --version 表示 rename from util-linux 2.x.x,但无论如何都是大规模重命名的好工具
    【解决方案3】:

    在 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'
    
    

    【讨论】:

      【解决方案4】:

      我对这个问题的解决方案是一个 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
      

      只需在执行脚本后将要应用脚本的目录名称作为参数。

      【讨论】:

        【解决方案5】:

        对于那些使用 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   
        

        【讨论】:

        • find . -depth -name '* *' \ | while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done 是在 Alpine Linux 上对我有用的唯一解决方案
        【解决方案6】:

        我用:

        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

        【讨论】:

        • 简单且在mac中工作。 (mac没有rename,用brew安装太难了..)
        • 很棒的答案。我使用了for d in *\ *; do mv "$d" "${d// /}"; done 未得分。
        • 与“find -name”答案不同,这个答案在我的 OS X 上工作!谢谢楼主!
        • 作为参考,这可以很容易地在 bash 中变成递归以使用 shopt -s globstarfor f in **/*\ *; do ...globstar 选项是 bash 内部的,而 rename 命令是常见的 Linux 工具,而不是 bash 的一部分。
        • ${f// /_}Bash variable expansionsearch and replace。 - f 是来自for 循环的变量,用于每个包含空格的文件。 - 第一个// 表示“全部替换”(不要在第一次出现时停止)。 - 然后`/_`表示“用下划线替换空格”
        【解决方案7】:

        Naidim's Answers 的递归版本。

        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
        

        【讨论】:

          【解决方案8】:

          对于名为 /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
          

          【讨论】:

            【解决方案9】:

            我只是为自己的目的制作一个。 可以作为参考。

            #!/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
            

            【讨论】:

              【解决方案10】:

              你可以用这个:

                  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
              

              【讨论】:

              • 使用 find 查看其他答案,您应该包含 -depth 标志来查找。否则,您可以在目录中的文件之前重命名目录。 dirname 和 basename 的问题相同,因此您不要尝试一步重命名 dir one/file two
              【解决方案11】:
              find . -depth -name '* *' \
              | while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done
              

              一开始没弄好,因为我没有想到目录。

              【讨论】:

              • 像魅力一样工作。谢谢迈克尔。
              • 丹尼斯,很好的接球,通过将IFS='' 放在read 前面很容易解决。此外,据我所知,其他 cmets 可以放弃 sort 步骤,将 -depth 选项改为 find
              • 如果文件名包含\ (反斜杠),则不起作用。可以通过添加-r 选项来读取来修复。
              • 这一定是我第 50 次访问此页面以复制和使用您的解决方案。 非常感谢。我更喜欢你的回答,因为我在 Mac 上,没有丹尼斯建议的 rename 命令。
              • @AlexConstantin,macports 没有rename?我从来没有费心去寻找,因为我认为这项任务不能证明实用性。如果你没有macports,你应该考虑安装它们;)
              【解决方案12】:

              您可以使用 Doug Harple 的 detox

              detox -r <folder>
              

              【讨论】:

                【解决方案13】:

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

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

                【讨论】:

                  【解决方案14】:

                  这是一个合理大小的 bash 脚本解决方案

                  #!/bin/bash
                  (
                  IFS=$'\n'
                      for y in $(ls $1)
                        do
                           mv $1/`echo $y | sed 's/ /\\ /g'` $1/`echo "$y" | sed 's/ /_/g'`
                        done
                  )
                  
                  【解决方案15】:

                  我发现了这个脚本,它可能很有趣:)

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

                  【讨论】:

                  • 文件名中带有换行符的文件失败。
                  【解决方案16】:

                  这个有点多。我用它来重命名我下载的种子(没有特殊字符(非 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,$_);
                      }
                  }
                  

                  【讨论】:

                    【解决方案17】:

                    这是一个(相当冗长的)find -exec 解决方案,它将“文件已存在”警告写入标准错误:

                    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 _
                    

                    【讨论】:

                      【解决方案18】:

                      bash 4.0

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

                      【讨论】:

                      • 看起来如果文件或目录名称中没有空格,这将对自身执行 mv(mv:无法移动 a' to a subdirectory of itself, a/a')
                      • 没关系。只需通过重定向到/dev/null 来删除错误消息。
                      • ghostdog,生成mv 55000 次只是为了重命名四个文件,即使您没有向用户发送消息,也可能会有点开销。
                      • krelin,即使 find 也会遍历您提到的那 55000 个文件以找到带有空格的文件,然后进行重命名。在后端,它仍在经历一切。如果需要,可以在重命名之前对空格进行初始检查。
                      • 我说的是生成 mv,而不是通过。 for file in *' '* 或类似的人不会做得更好吗?
                      【解决方案19】:

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

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

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

                      【讨论】:

                      • 当我运行你的时,我没有得到任何改变。
                      • 检查 util-linux 设置:$ rename --version rename (util-linux-ng 2.17.2)
                      • Grepping /usr/bin/rename(Perl 脚本)显示“Revision 1.5 1998/12/18 16:16:31 rmb1”
                      • 在我的系统上它被命名为rename.ul
                      • 在我的跑步过程中只改变了 一个 空间,所以“go tell fire on the mountain”变成了“go_tell fire on the mountain”。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-09-26
                      • 1970-01-01
                      • 2015-11-13
                      • 2022-01-07
                      • 2017-11-09
                      • 2013-04-30
                      • 1970-01-01
                      相关资源
                      最近更新 更多