使用 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。这里x和y是一个词。现在这可能不是 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;
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
awk
和 sub
修改给定的输入字符串(这里,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 所有这三个,那么脚本将不再需要任何参数处理或检查。
我有一个包含很多 java 文件的目录,在每个文件中我都有一个 class 变量:
String system = "x";
我希望能够创建一个在同一目录中执行的 bash 脚本,它将仅转到目录中的 java 文件,并替换 x 的 this 实例,带有 y。这里x和y是一个词。现在这可能不是 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;
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
awk
和sub
修改给定的输入字符串(这里,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 所有这三个,那么脚本将不再需要任何参数处理或检查。