【问题标题】:Change multiple files更改多个文件
【发布时间】:2012-05-13 19:51:11
【问题描述】:

以下命令正确更改了 2 个文件的内容。

sed -i 's/abc/xyz/g' xaa1 xab1 

但我需要做的是动态更改几个这样的文件,我不知道文件名。我想编写一个命令,从当前目录中读取所有以xa* 开头的文件,sed 应该更改文件内容。

【问题讨论】:

标签: sed


【解决方案1】:

我很惊讶没有人提到 find 的 -exec 参数,它是为这种类型的用例设计的,尽管它会为每个匹配的文件名启动一个进程:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

或者,可以使用 xargs,这将调用更少的进程:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

或者更简单地在 find 中使用 + exec variant 而不是 ; 以允许 find 为每个子进程调用提供多个文件:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +

【讨论】:

  • 我不得不像这样修改这个答案中的命令:find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \; 这是查找命令./ 的位置,-i 之后的一对单引号用于 OSX。
  • find 命令的工作原理是由 elfonso 提供的,./ 等于 . 并且在 -i 之后只有 backupsuffix 参数。
  • find 的-exec 选项和{} + 足以解决上述问题,并且可以满足大多数要求。但是xargs 通常是更好的选择,因为它还允许使用-p 选项进行并行处理。当您的 glob 扩展大到足以超出命令行长度时,您可能还会受益于顺序运行的加速。
【解决方案2】:

更好:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

因为没有人知道有多少文件,而且很容易打破命令行限制。

当文件过多时会发生以下情况:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#

【讨论】:

  • 如果有那么多文件,您将打破for 命令中的命令行限制。为了保护自己免受这种情况的影响,您必须使用find ... | xargs ...
  • 我不知道实现,但“xa*”模式确实必须在某些时候得到扩展。 shell 对for 的扩展与对echogrep 的扩展是否不同?
  • 查看更新后的答案。如果您需要更多信息,请提出官方问题,以便人们可以帮助您。
  • 在 sed 命令中,您需要使用 "$i" 而不是 $i 以避免对带有空格的文件名进行分词。否则这是非常好的。
  • 关于列表,我相信不同之处在于for 是语言语法的一部分,甚至不仅仅是内置的。对于sed -i 's/old/new' ** 的扩展必须全部作为 arglist 传递给 sed,我很确定这必须在 sed 进程启动之前发生。使用 for 循环,完整的 arglist(* 的扩展)永远不会作为命令传递,只存储在 shell 内存中并迭代。不过,我对此没有任何参考,但这似乎很可能是不同的。 (我很想听听知识渊博的人...)
【解决方案3】:

您可以同时使用 grep 和 sed。这允许您递归搜索子目录。

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)

【讨论】:

  • 这种方法对我来说的好处是我可以加入grep -v 以避免git文件夹grep -rl &lt;old&gt; . | grep -v \.git | xargs sed -i 's/&lt;old&gt;/&lt;new&gt;/g'
  • Mac 的最佳解决方案!
【解决方案4】:

这些命令在 Mac OS X 附带的默认 sed 中不起作用。

来自man 1 sed

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

试过

sed -i '.bak' 's/old/new/g' logfile*

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

两者都可以正常工作。

【讨论】:

  • @sumek 这是 OS X 上的示例终端会话,显示 sed 替换了所有出现:GitHub Gist
  • 我用它来替换我所有网站配置文件中的两个不同的行,下面是一行。 sed -i.bak "s/supercache_proxy_config/proxy_includes\/supercache_config/g; s/basic_proxy_config/proxy_include\/basic_proxy_config/g" sites-available/* 完成文件后不要忘记删除 *.bak 文件为了系统卫生。
【解决方案5】:

@PaulR 将此作为评论发布,但人们应该将其视为答案(这个答案最适合我的需要):

sed -i 's/abc/xyz/g' xa*

这适用于中等数量的文件,可能在几十个数量级,但probably not on the order of millions

【讨论】:

  • 假设你的替换中有正斜杠。另一个文件路径示例sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn.
【解决方案6】:

另一种更通用的方法是使用find

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')

【讨论】:

  • 该 find 命令的输出被扩展,所以这并不能解决问题。相反,您应该使用 -exec
  • @erjoalgo 之所以有效,是因为 sed 命令可以处理多个输入文件。正是需要扩展 find 命令才能使其工作。
  • 只要文件数不超过命令行限制,它就可以工作。
  • 该限制仅取决于机器可用的内存资源,它与 exec 的限制完全相同。
  • 这根本不是真的。在上面的命令中, $(find . ...) 被扩展为一个命令,如果有很多匹配的文件,这个命令可能会很长。如果它太长(例如在我的系统中限制大约为 2097152 个字符),您可能会收到错误消息:“参数列表太长”并且命令将失败。请谷歌此错误以获取有关此的一些背景信息。
【解决方案7】:

我正在使用find 来完成类似的任务。这很简单:你必须像这样将它作为sed 的参数传递:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

这样你不必编写复杂的循环,而且很容易看出你要更改哪些文件,只需在运行sed之前运行find

【讨论】:

【解决方案8】:

你可以做

'xxxx' 文本 u 搜索并将其替换为 'yyyy'

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'

【讨论】:

    【解决方案9】:

    如果您能够运行脚本,以下是我针对类似情况所做的:

    使用字典/hashMap(关联数组)和sed 命令的变量,我们可以循环遍历数组来替换多个字符串。在 name_pattern 中包含通配符将允许在特定目录 (source_dir) 中用模式(可能类似于 name_pattern='File*.txt' )替换文件中的内容。 所有的改动都写在logfiledestin_dir

    #!/bin/bash
    source_dir=source_path
    destin_dir=destin_path
    logfile='sedOutput.txt'
    name_pattern='File.txt'
    
    echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
    echo "Source_DIR=$source_dir destin_DIR=$destin_dir "
    
    declare -A pairs=( 
        ['WHAT1']='FOR1'
        ['OTHER_string_to replace']='string replaced'
    )
    
    for i in "${!pairs[@]}"; do
        j=${pairs[$i]}
        echo "[$i]=$j"
        replace_what=$i
        replace_for=$j
        echo " "
        echo "Replace: $replace_what for: $replace_for"
        find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
        find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
    done
    
    echo " "
    echo "----End $(date)---" | tee -a $destin_dir/$logfile
    

    首先声明pairs数组,每对是一个替换字符串,然后WHAT1将被替换为FOR1OTHER_string_to replace将被替换为文件string replaced中的string replaced。在循环中读取数组,该对的第一个成员被检索为replace_what=$i,第二个成员被检索为replace_for=$jfind 命令在目录中搜索文件名(可能包含通配符),sed -i 命令在相同文件中替换先前定义的内容。最后我添加了一个grep 重定向到日志文件来记录文件中所做的更改。

    这在GNU Bash 4.3sed 4.2.2 中对我有用,并且基于 VasyaNovikov 对Loop over tuples in bash 的回答。

    【讨论】:

      猜你喜欢
      • 2020-10-23
      • 2020-06-28
      • 2017-06-06
      • 1970-01-01
      • 2019-11-14
      • 2017-06-29
      • 2018-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多