使用 bash 在 java 文件中找到包含模式的行,然后替换该行的另一部分

Use bash to find line in java files which include a pattern, and then replace another part of the line

我有一个包含很多 java 文件的目录,在每个文件中我都有一个 class 变量:

String system = "x";

我希望能够创建一个在同一目录中执行的 bash 脚本,它将仅转到目录中的 java 文件,并替换 x 的 this 实例,带有 y。这里xy是一个词。现在这可能不是 java 脚本中单词 x 的唯一实例,但它肯定是第一个。

我希望能够在类似于以下的命令行中执行我的脚本:

changesystem.sh -x -y

这样我就可以指定 x 应该是什么,以及我希望将其替换的 y 。我找到了一种方法来查找和打印找到第一个模式实例的行号:

awk '[=14=] ~ /String system/ {print NR}' file

然后我找到了如何使用以下方法替换给定行上的子字符串:

awk 'NR==line_number { sub("x", "y") }'

但是,我还没有找到将它们结合起来的方法。也许还有更简单的方法?或者,还有更好更高效的方法吗?

任何 help/advice 将不胜感激

您可以使用以下 GNU awk 脚本创建 changesystem.sh 文件:

#!/bin/bash
for f in *.java; do
    awk -i inplace -v repl="" '
        !x && /^\s*String\s+system\s*=\s*".*";\s*$/{
            lwsp=gensub(/\S.*/, "", 1);
            print lwsp"String system = \""repl"\";";
            x=1;next;
        }1' "$f";
done;

或者,任何 awk:

#!/bin/bash
for f in *.java; do
    awk -v repl="" '
        !x && /^[[:space:]]*String[[:space:]]+system[[:space:]]*=[[:space:]]*".*";[[:space:]]*$/{
            lwsp=[=11=]; sub(/[^[:space:]].*/, "", lwsp);
            print lwsp"String system = \""repl"\";";
            x=1;next
        }1' "$f" > tmp && mv tmp "$f";
done;

然后,make the file executable

chmod +x changesystem.sh

然后,运行喜欢

./changesystem.sh 'new_value'

备注:

  • for f in *.java; do ... done 遍历当前目录下的所有 *.java 文件
  • -i inplace - 用于执行内联替换的 GNU awk 功能(在 non-GNU awk 中不可用)
  • -v repl="" 将脚本的第一个参数传递给 awk 命令
  • !x && /^\s*String\s+system\s*=\s*".*";\s*$/ - 如果 x 为假并且记录以任意数量的空格(\s*[[:space:]]*)开始,则 String,任何1+ 个空格,system= 包含任何零个或多个空格,然后是一个 " 字符,然后是任何文本并以 "; 和任何零个或多个空格结尾空格,然后
  • lwsp=gensub(/\S.*/, "", 1); 将前导空格放入 lwsp 变量(它从匹配的行中删除以第一个 non-whitespace 字符开头的所有文本)
  • lwsp=[=32=]; sub(/[^[:space:]].*/, "", lwsp); - 与上面相同,只是方式不同,因为 non-GNU 不支持 gensub awksub 修改给定的输入字符串(这里,lwsp
  • {print "String system = \""repl"\";";x=1;next}1 - 打印 String system = " + 替换字符串 + ";,将 1 分配给 x,然后移至下一行,否则, 仅按原样打印该行。

您不需要 pre-compute 行号。整个工作可以通过一个 not-too-complicated sed 命令完成。不过,您可能确实想要编写脚本。例如:

#!/bin/bash

[[ $# -eq 3 ]] || {
  echo "usage: [=10=] <context regex> <target regex> <replacement text>" 1>&2
  exit 1
}

sed -si -e "// { s/\<\>//; t1; p; d; :1; n; b1; }" ./*.java

假设要修改的文件是当前工作目录中的 java 个源文件,我相信您理解(松散的)参数检查和用法消息。

至于 sed 命令本身,

  • -s 选项指示 sed 将每个参数视为一个单独的流,而不是像将所有输入连接到一个长流中那样操作。

  • -i选项指示sed修改指定文件in-place.

  • sed 表达式对每一行执行默认操作(逐字打印),除非该行与第一个脚本参数给出的“上下文”模式匹配。

  • 对于与上下文模式匹配的行,

    • s/\<\>// - 尝试执行想要的替换

      • \<\> 分别匹配单词的开始和结束边界,因此指定的模式将不会匹配部分单词(尽管如果目标模式允许,它可以匹配多个完整的单词)
    • t1 - 如果进行了替换,则分支到标签 1,否则

    • p; d - 打印当前行并立即开始下一个循环

    • :1; n; b1 - 标签 1(只能通过分支到达):打印当前行并读取 n 下一行,然后循环回到标签 1。这将打印文件的其余部分,而无需任何更多测试或替换。

用法示例:

/path/to/replace_first.sh 'String system' x y

值得注意的是,这确实让用户了解了 sed 对正则表达式和替换文本的解释的一些细节,尽管这并没有体现在示例用法中。


请注意,如果您确定要修改目标在每个文件中的整体首次外观,可以通过删除上下文模式位来简化。您还可以 hard-code 上下文、目标模式,and/or 替换文本。如果您 hard-code 所有这三个,那么脚本将不再需要任何参数处理或检查。