【问题标题】:How to replace string with random line from text file multiple times如何用文本文件中的随机行多次替换字符串
【发布时间】:2014-12-18 15:33:29
【问题描述】:

我有一个脚本,旨在通过文件旋转并用文件中的随机行替换占位符 {{su}},占位符在文件中出现多次,我需要它是每次随机。目前它用同一行替换每个占位符。

#!/bin/bash

subject=$(shuf -n1 *.subjects)
    cat tmp.$file | sed -e "s/{{su}}/$subject/" > output.file

【问题讨论】:

  • 我看过这个,虽然这与我的问题有关,但它涵盖了一个不同的问题,解决方案是不相关的。
  • 您似乎没有发布所有代码...$file 来自哪里?
  • 抱歉,该文件与名为“*.subjects”的文件位于同一目录中

标签: linux bash random sed shuffle


【解决方案1】:

接受的答案有细微的缺陷:

  • 如果{{su}}在同一行出现多次,则相同替换该行的每个模式{{su}}
  • 因为read 不与IFS=-r 开关一起使用,你会得到其他令人讨厌的惊喜:不一定会保留空格,你会得到反斜杠解释(但这很容易修复),
  • 如果替换字符串包含斜杠或其他有趣的字符,sed 会混淆。

一种有效的方法,但需要读取内存中的整个文件(它只适用于拥有少量{{su}} 的小文件):

#!/bin/bash

file=$(< filename.txt )

while [[ $file = *'{{su}}'* ]]; do
    repl=$(shuf -n1 file.subjects)
    file=${file/'{{su}}'/"$repl"}
done
printf '%s\n' "$file"

对于类似于接受的答案的方法,即逐行阅读:

#!/bin/bash

while IFS= read -r line; do
    while [[ $line = *'{{su}}'* ]]; do
        repl=$(shuf -n1 file.subjects)
        line=${line/'{{su}}'/"$repl"}
    done
    printf '%s\n' "$line"
done < filename.txt

现在关于选择随机行的方法:虽然shuf 很好,但它是一个外部进程,并且由于它将被多次调用(在子shell 中),您可以考虑在 Bash 中实现类似的东西。如果您的行数有限,您可以考虑将所有行放入一个数组中,然后从该数组中随机选择一个条目:

#!/bin/bash

mapfile -t replacements < file.subjects
nb_repl=${#replacements[@]}

while IFS= read -r line; do
    while [[ $line = *'{{su}}'* ]]; do
        repl=${replacements[RANDOM%nb_repl]}
        line=${line/'{{su}}'/"$repl"}
    done
    printf '%s\n' "$line"
done < filename.txt

这仅适用于file.subjects 中的“少量”行(小,理解少于 32767),并且如果您不太担心通过模获得的分布。不过,有非常简单的解决方法可以解决此问题。

注意。您使用的是shuf -n1 *.subjects。使用多个文件调用shuf 是错误的(至少对于我的shuf 版本)。因此,如果 glob *.subjects 扩展到多个文件,则会出现错误。

注意。如果您不想陷入无限循环,请确保替换不包含 {{su}} 模式!

【讨论】:

  • 我可以使用第二种方法来替换多个字符串吗?即{{su}},{{fr}}...那么第四?
  • @BigEarl:当然!您可以堆叠line=${line/'{{su}}'/"$repl_su"}line=${line/'{{fr}}'/"$repl_fr"} 等形式的行,其中替换repl_surepl_fr 已适当定义。
  • 那么我会使用通配符吗? while [[ $line = *'{{su}}'* ]]; dowhile [[ $line = *'{{*}}'* ]]; do ??或使用某种正则表达式 OR 语句? 感谢您的帮助
  • @BigEarl 哦,抱歉,我在之前的评论中没有解决这个问题。您的解决方案是正确的,但可能有一些警告(和无限循环)。另一种更安全的方法是,只关注{{su}}{{fr}} 是使用扩展glob:while [[ $line = *'{{'@(su|fr)'}}'* ]]; do
  • @BigEarl:我想安全比效率更重要:所以你有两种选择:一种是使用扩展的 glob,就像我展示的那样,另一种是使用正则表达式:[[ $line =~ '{{'(fr|su)'}}' ]]。我做了一个非常快速的基准测试,它似乎 extglob [[ $line = *'{{'@(su|fr)'}}'* ]] 解决方案更快。
【解决方案2】:

在这种情况下,您需要在文件中逐行迭代并在每次迭代时生成随机字符串。它将检查所有行中的{{su}} 模式,如果找到,它将用另一个文件中的随机字符串替换它:

while read line
do
subject=$(shuf -n1 *.subjects)
sed -e "s/{{su}}/$subject/g" <<< "$line")
done <1.txt

【讨论】:

  • 谢谢,我把它变成了一个班轮,效果很好。
【解决方案3】:

你需要一个循环。首先使用wc -l 计算tmp.$file 中的行数。 然后循环计数,每次执行你拥有的两行shell脚本。 所以在每个循环中,你都会得到一个新的主题并执行一个新的sed。诀窍是使用sed 命令的地址、地址格式一次对一行执行替换,并传入地址的循环计数器。

类似(这里是伪代码):

$count = $(wc -l tmp.$file)    
$i=1    
cp tmp.$file > output.file    
while $i < $count    
 subject = $(shuf -n1 *.subjects)    
 cat output.file | sed -e "$i,$is/{{su}}/$subject/" > output.file    
 $i=$i+1    
end while

【讨论】:

    猜你喜欢
    • 2019-08-14
    • 1970-01-01
    • 1970-01-01
    • 2016-09-06
    • 1970-01-01
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 2015-01-20
    相关资源
    最近更新 更多