【问题标题】:Prepend a # to the first line not already having a #在没有 # 的第一行添加 #
【发布时间】:2016-02-24 22:30:00
【问题描述】:

我有一个文件,其中包含我运行的命令的选项。每当我运行命令时,我希望它使用第一行中定义的未注释掉的选项运行。我使用这个 bash 脚本来做到这一点:

while read run opt c; do
  [[ $run == \#* ]] && continue
  ./submit.py $opt $run -c "$c"
  break
done < to_submit.txt

文件 to_submit.txt 有这样的条目:

#167993 options/optionfile.py long description
167995 options/other_optionfile.py other long description
...

使用最后一个未注释行中的选项运行提交脚本后,我想在命令成功运行后注释掉该行。

我可以找到我使用的选项的行号,并将其添加到 while 循环中:

line=$(grep -n "$run" to_submit.txt | grep "$opt" | grep "$c" | cut -f 1 -d ":")

但我现在不确定如何在该行之前实际添加 # 。我可能可以使用 head 和 tail 来保存其他行并单独处理该行并将其全部组合回文件中。但这听起来很复杂,必须有更简单的 sed 或 awk 解决方案。

【问题讨论】:

    标签: awk sed


    【解决方案1】:
    $ awk '!f && sub(/^[^#]/,"#&"){f=1} 1' file
    #167993 options/optionfile.py long description
    #167995 options/other_optionfile.py other long description
    ...
    

    要覆盖原始文件的内容:

    awk '!f && sub(/^[^#]/,"#&"){f=1} 1' file > tmp && mv tmp file
    

    就像任何其他 UNIX 命令一样。

    【讨论】:

    • 也是不错的答案,尽管我发现 mklement0 的 sed 答案更具可读性。我知道我的 awk 知识太少是为什么我一开始就找不到更好的解决方案。
    【解决方案2】:

    使用 GNU sed 在这里可能是最简单的:

    sed '0,/^[^#]/ s//#&/' file
    

    如果您想就地更新file,请添加选项-i

    • '0,/^[^#]/ 匹配所有行,包括第一个不以 # 开头的行
    • s//#&amp;/ 然后将 # 添加到该行。
      • 请注意,s//.../(即 正则表达式)会重复使用范围内最后一个匹配的正则表达式,在本例中为 /^[^#]/

    请注意,该命令不适用于 BSD/OSX sed,不幸的是,因为以 0 开头的范围以允许范围 endpoint 匹配非常那里也不支持第一行。 可以使该命令与 BSD/OSX sed 一起工作,但比较麻烦。

    【讨论】:

    • 我知道必须有一些 sed fu 才能做到这一点。它看起来也比 Ed 的 awk oneline 更具可读性。
    • 是的,它比awk 答案更直接地表达了意图,但是表达式所采用的 form 和隐式应用的规则可能仍然难以理解外行。
    • 最终使用了这个解决方案。
    • 是的,但话又说回来,几乎所有的 sed 对我来说都像魔术:)
    • @Graipher - 为了避免神奇的符文,这里是您需要了解的关于 sed 的全部内容:sed -n 's/regexp/string/gp'。对于其他任何事情,只需使用 awk。有时 sed 解决方案会更简洁一些,甚至可能看起来更清晰一些,但不值得为这几个案例烦恼,因为它可能不是可移植的(如本例所示)并且肯定不会是可扩展的随着需求的变化等。只要确保您了解 sed 命令 s、g 和 p(带 -n)并阅读 Arnold Robbins 的《Effective Awk Programming, 4th Edition》一书,了解如何做其他事情。
    【解决方案3】:

    如果输入/输出文件不是很大,都可以在 Bash 中完成:

    optsfile=to_submit.txt
    
    has_run_cmd=0
    outputlines=()
    while IFS= read -r inputline || [[ -n $inputline ]] ; do
        read run opt c <<<"$inputline"
        if (( has_run_cmd )) || [[ $run == \#* ]] ; then
            outputlines+=( "$inputline" )
        elif ./submit.py "$opt" "$run" -c "$c" ; then
            has_run_cmd=1
            outputlines+=( "#$inputline" )
        else
            exit $?
        fi
    done < "$optsfile"
    
    (( has_run_cmd )) && printf '%s\n' "${outputlines[@]}" > "$optsfile"
    

    文件的行放在outputlines 数组中,并在./submit.py 命令中使用的行前面加上一个哈希。如果命令成功运行,文件将被outputlines 中的行覆盖。

    【讨论】:

      【解决方案4】:

      经过一番搜索,我发现了

      awk -v run="$run" -v opt="$opt" '{if($1 == run && $2 == opt) {print "#" $0} else print}' to_submit.txt > temp
      mv -b -f temp to_submit.txt
      

      似乎解决了这个问题(不需要先找到行号,只需比较 $run 和 $opt)。这假设 run 和 opt 的组合足以识别一行并且不需要注释(在我的情况下恰好是真的)。不确定如何考虑跨 awk 中多个字段的注释。

      【讨论】:

      • 不能保证cat to_submit.txt 会在tee to_submit.txt 截断它之前读取其输入的全部内容。最好将您的输出写入一个临时文件,然后在替换成功后将该文件重命名为您的原始输入。
      • 永远不要写入您正在阅读的文件,原因是@chepner 声明。还要避免使用 UUOC(谷歌搜索)。
      • 是的,我只是通过擦除整个文件自己发现了这一点……幸好我有备份。使用临时文件编辑,不使用 cat。
      猜你喜欢
      • 2022-11-17
      • 1970-01-01
      • 2016-04-07
      • 1970-01-01
      • 2021-02-11
      • 1970-01-01
      • 1970-01-01
      • 2012-08-08
      • 1970-01-01
      相关资源
      最近更新 更多