【问题标题】:bash scripting challengebash 脚本挑战
【发布时间】:2011-01-08 08:08:53
【问题描述】:

我需要编写一个 bash 脚本,它将遍历目录(包括子目录)的内容并执行以下替换:

  • 将任何文件名中的“foo”替换为“bar”
  • 将任何文件内容中的“foo”替换为“bar”

到目前为止,我所拥有的是

find . -name '*' -exec {} \;

:-)

【问题讨论】:

  • 因为小时候父母没有给我足够的爱
  • ahaha 我会给你 +1 的评论。现在,这是作业吗?
  • sed 和 awk 是你的朋友,但 Python 会是这方面更好的盟友
  • 显然和一行perl是互斥的。
  • 如果你足够努力地眯着眼睛,Groovy 和 Python 就会开始看起来一样

标签: bash scripting


【解决方案1】:
#!/bin/bash
function RecurseDirs
{
oldIFS=$IFS
IFS=$'\n'
for f in *
do
  if [[ -f "${f}" ]]; then
    newf=`echo "${f}" | sed -e 's/foo/bar/g'`
    sed -e 's/foo/bar/g' < "${f}" > "${newf}"
  fi
  if [[ -d "${f}" && "${f}" != '.' && "${f}" != '..' && ! -L "${f}" ]]; then
    cd "${f}"
    RecurseDirs .
    cd ..
  fi
done
IFS=$oldIFS
}
RecurseDirs .

【讨论】:

  • 非常好,可怕的语言:)
  • @Don:听起来像是复制和粘贴错误。请尝试输入此内容。 @dreynold:注意符号链接,您可能会陷入目录循环或完全转到其他地方,因为 .. 可能不是您认为的那样。 (是的,Bash 通常会尝试解决此问题,但这是一种启发式方法。请参阅 *.com/questions/2105572
  • @ephemient:是的,你是对的——这不是最安全的事情。更正了,我想。
【解决方案2】:

gregb 对 find-exec 方法的更正(添加引号):

# compare 

bash -c '
   echo $'a\nb\nc'
'

bash -c '
   echo $'"'a\nb\nc'"'
'

# therefore we need

find . -type f -exec bash -c ' 
...
ed -s "${path}" <<< $'"'H\ng/foo/s/foo/bar/g\nwq'"'; 
...
' \;

【讨论】:

    【解决方案3】:

    另一个 find-exec 解决方案:

    find . -type f -exec bash -c '
    path="{}"; 
    dirName="${path%/*}";
    baseName="${path##*/}";
    nbaseName="${baseName/foo/bar}";
    #nbaseName="${baseName//foo/bar}";
    # cf. http://www.bash-hackers.org/wiki/doku.php?id=howto:edit-ed
    ed -s "${path}" <<< $'H\ng/foo/s/foo/bar/g\nwq';
    #sed -i "" -e 's/foo/bar/g' "${path}";  # alternative for large files
    exec mv -iv "{}" "${dirName}/${nbaseName}"
    ' \;
    

    【讨论】:

    • 如果文件名包含“或\,你应该使用更像-exec bash -c 'path=$1; ...' - {} \;的东西。
    【解决方案4】:

    for f in `tree -fi | grep foo`; do sed -i -e 's/foo/bar/g' $f ; done

    【讨论】:

      【解决方案5】:

      bash 4.0

      #!/bin/bash
      shopt -s globstar
      path="/path"
      cd $path
      for file in **
      do
          if [ -d "$file" ] && [[ "$file" =~ ".*foo.*" ]];then
              echo mv "$file" "${file//foo/bar}"
          elif [ -f "$file" ];then
              while read -r line
              do
                  case "$line" in
                      *foo*) line="${line//foo/bar}";;
                  esac
                  echo "$line"
              done < "$file"  > temp
              echo mv temp "$file"
      
          fi
      done
      

      删除“回声”以提交更改

      【讨论】:

      • 次要问题:如果 $line'-e''-n' 或 Bash echo 识别的其他开关,它将在文件中被破坏。无条件地执行替换并没有什么坏处,所以我会将整个 while 身体削减到 printf '%s\n' "${line//foo/bar}"。另一个笨蛋:你已经在使用 Bash 的双括号测试,为什么不写更短的[[ $file = *foo* ]]?当更高级别的目录名发生更改并且您正在处理目录树下的某些内容并且没有重命名非目录时,您也会遇到更难解决的问题(这应该发生,通过我阅读 OP 的问题)。
      【解决方案6】:

      更新时间:美国东部标准时间 1632 年

      • 现在可以处理空白,但“读取项目时”永远不会终止。更好的, 但仍然不对。将继续 正在处理这个问题。

      aj@mmdev0:~/foo_to_bar$ cat script.sh

      #!/bin/bash
      
      dirty=true
      while ${dirty}
      do
          find ./ -name "*" |sed -s 's/ /\ /g'|while read item
          do
              if [[ ${item} == "./script.sh" ]]
              then
                  continue
              fi
              echo "working on: ${item}"
      
              if [[ ${item} == *foo* ]]
              then
                  rename 's/foo/bar/' "${item}"
                  dirty=true
                  break
              fi
      
              if [[ ! -d ${item} ]]
              then
                  cat "${item}" |sed -e 's/foo/bar/g' > "${item}".sed; mv "${item}".sed "${item}"
              fi
              dirty=false
          done
      done
      

      【讨论】:

      • 如果任何文件名包含空格,for item in `...` 会出错。
      【解决方案7】:
      find "$@" -depth -exec sed -i -e s/foo/bar/g {} \; , -name '*foo*' -print0 |
      while read -d '' file; do
          base=$(basename "$file")
          mv "$file" "$(dirname "$file")/${base//foo/bar}"
      done
      

      【讨论】:

      • 遗憾的是,, 运算符不是标准的,并且似乎只存在于 GNU find 中。此外,这会将目录和文件传递到sed,您需要添加-type f 以避免错误。我喜欢它,因为它比这里的大多数其他答案更简洁。
      • 好吧,sed 将在目录上出错,但 find 将继续 ;-) 如果没有 , 我认为这将不得不分成两个 find 调用或 make Bash 部分做更多的工作。
      【解决方案8】:

      与 RH rename:

      find -f \( -exec sed -i s/foo/bar/g \; , -name \*foo\* -exec rename foo bar {} \; \)
      

      【讨论】: