操作系统发行版:Ubuntu 22.04.3 LTS
gawk 版本:GNU Awk 5.1.0,API:3.0(GNU MPFR 4.1.0,GNU MP 6.2.1)
我有一个文本文件,每隔一段时间,一行就会有一个显着的一定量的空白,后跟一些随机文本。我正在使用 gawk 搜索这些行并对行的左侧部分进行修改。
输入:
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
预期输出:
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
WORDWRAP random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
WORDWRAP random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
此命令有效并返回预期输出:
gawk '/^[[:space:]]{75}/ { $0 = substr($0,1,15) " WORDWRAP " substr($0,26) }1' input.txt
我想要做的是将变量分配给间隔表达式上的重复间隔以及 substr 函数上的起始长度值,因为这些值可能会根据输入文件而变化。
我设置了三个环境变量:
export PH1="75"; export PH2="15"; export PH3="26"
然后尝试运行此命令:
gawk -v gph1="${PH1}" -v gph2="${PH2}" -v gph3="${PH3}" '/^[[:space:]]{gph1}/ { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1' input.txt
它只返回未修改的输入。如果我将重复间隔设置为实际值并将变量保留在 substr 起始值和长度值上:
gawk -v gph2="${PH2}" -v gph3="${PH3}" '/^[[:space:]]{75}/ { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1' input.txt
它可以工作并返回预期的输出。
我也尝试过但没有成功,因为它只是返回未修改的输入:
gawk '/^[[:space:]]{ENVIRON["PH1"]}/ { $0 = substr($0,1,ENVIRON["PH2"]) " WORDWRAP " substr($0,ENVIRON["PH3"]) }1' input.txt
但是,当将重复间隔设置为实际值时,这确实有效:
gawk '/^[[:space:]]{75}/ { $0 = substr($0,1,ENVIRON["PH2"]) " WORDWRAP " substr($0,ENVIRON["PH3"]) }1' input.txt
有没有办法在区间表达式中使用变量作为重复区间?
(2023-09-30 添加)这个问题的答案是肯定的,但不是在正则表达式常量中。根据收到的反馈,我最终将命令更改为:
gawk -v gph1="${PH1}" -v gph2="${PH2}" -v gph3="${PH3}" ' $0 ~ "^[[:blank:]]{" gph1 "}" { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1' input.txt
答案1
您可以在BEGIN{...}
块中构建所需的正则表达式,例如:
BEGIN { regex = "^[[:space:]]{" gph1 "}" }
然后在主脚本中将输入行 ( $0
) 与正则表达式进行比较,例如:
# replace this:
/^[[:space:]]{gph1}/
# with this:
$0 ~ regex
将这些更改滚动到当前gawk
脚本中:
gawk -v gph1="${PH1}" -v gph2="${PH2}" -v gph3="${PH3}" '
BEGIN { regex = "^[[:space:]]{" gph1 "}" }
$0 ~ regex { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }
1
' input.txt
这会生成:
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
WORDWRAP random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
WORDWRAP random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
答案2
使用任何 POSIX awk:
$ cat tst.sh
PH1='75'; PH2='15'; PH3='26'
awk -v gph1="$PH1" -v gph2="$PH2" -v gph3="$PH3" '
$0 ~ "^[[:space:]]{"gph1"}" { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1
' input.txt
$ ./tst.sh input.txt
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
WORDWRAP random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
WORDWRAP random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
上面使用动态(又名“计算”)正则表达式而不是文字(又名“常量”)正则表达式,请参阅https://www.gnu.org/software/gawk/manual/gawk.html#Compulated-Regexps。
不过,您实际上不应该传入硬编码gph3
,因为该位置取决于替换文本的长度,因此您应该传入替换文本并gph3
根据其长度进行计算,以便您可以根据需要更改替换文本,而无需gph3
这样做时必须手动重新计算:
$ cat tst.sh
PH1='75'; PH2='15'; new=' WORDWRAP '
awk -v gph1="$PH1" -v gph2="$PH2" -v new="$new" '
BEGIN { gph3 = gph2 + length(new) + 1 }
$0 ~ "^[[:space:]]{"gph1"}" { $0 = substr($0,1,gph2) new substr($0,gph3) }1
' input.txt
但是,回到最初的问题......
您也可以将动态正则表达式存储在变量中,这样您只需在脚本执行开始时进行字符串连接来构造正则表达式一次,而不是每次读取输入行时:
awk -v gph1="$PH1" -v gph2="$PH2" -v gph3="$PH3" '
BEGIN { re = "^[[:space:]]{"gph1"}" }
$0 ~ re { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1
' input.txt
如果您愿意,您可以re
在命令行而不是在脚本内定义该变量:
awk -v re="^[[:space:]]{$PH1}" -v gph2="$PH2" -v gph3="$PH3" '
$0 ~ re { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1
' input.txt
使用 GNU awk,您也可以定义一个变量来包含强类型正则表达式常量然后使用它:
$ cat tst.sh
PH1="75"; PH2="15"; PH3="26"
gawk -v re="@/^[[:space:]]{$PH1}/" -v gph2="$PH2" -v gph3="$PH3" '
$0 ~ re { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1
' input.txt
$ ./tst.sh input.txt
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
WORDWRAP random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
WORDWRAP random_text
formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text formatted_text
如果您更喜欢在脚本内动态创建强类型正则表达式 ( re
) 而不是在命令行上,您可以这样做,但在我看来,语法看起来有点笨拙,因为与字符串不同,强类型正则表达式没有连接运算符,但是,像字符串一样,您可以*sub()
对它们执行 a 操作,它们将保留其类型,因此其中任何一个都可以工作(x
可以是您喜欢的任何字符或字符串,它只是要sub()
执行操作的占位符):
gawk -v gph1="$PH1" -v gph2="$PH2" -v gph3="$PH3" '
BEGIN { re = @/^[[:space:]]{x}/; sub(/x/,gph1,re) }
$0 ~ re { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1
' input.txt
gawk -v gph1="$PH1" -v gph2="$PH2" -v gph3="$PH3" '
BEGIN { re = @/x/; sub(/x/,"^[[:space:]]{"gph1"}",re) }
$0 ~ re { $0 = substr($0,1,gph2) " WORDWRAP " substr($0,gph3) }1
' input.txt
对于 OP 情况,使用强类型正则表达式相对于常规动态正则表达式没有任何好处,我只是在这里展示它们,因为它们是一个选项,并且在其他上下文中可能有用,请参阅傻瓜手册。
在这种情况下,强类型正则表达式的唯一微小好处是,如果操作员想要在正则表达式中使用\s
而不是使用[[:space:]]
,那么他们就不必记住添加额外的反斜杠\s
(当包含动态正则表达式的字符串在内部使用时使用)转换为正则表达式):
$ echo 'foo bar' | gawk -v re='\s' '$0 ~ re'
gawk: warning: escape sequence `\s' treated as plain `s'
$ echo 'foo bar' | gawk -v re='\\s' '$0 ~ re'
foo bar
$ echo 'foo bar' | gawk -v re='@/\s/' '$0 ~ re'
foo bar
答案3
您不能{repeat}
在常量正则表达式中使用 awk var 或 envvar (或任何其他非文字) for ,但您可以仅测试第一个 var 列:
substr($0,1,ph1) ~ /^[[:space:]]+$/
# or equivalent but perhaps confusing
substr($0,1,ph1) !~ /[^[:space:]]/
# ENVIRON["PH1"] if you don't make it awk var
但你真的想在这里匹配 HT FF VT CR 这样的字符吗?从您对数据的描述来看,我认为您只想匹配实际的空格字符,而不是空格字符类其中包括更多。为了那个原因
substr($0,1,ph1) == sprintf("%.*s",ph1,"")
或为了效率
BEGIN{ ph1spaces = sprintf("%.*s",ph1,"") } substr($0,1,ph1)==ph1spaces { do change }
或者,您可以使用动态正则表达式,但每次都必须重新编译
$0 ~ sprintf("^[[:space:]]{%d}", ph1)
# or for actual space only
$0 ~ sprintf("^ {%d}", ph1)
虽然您只要求 gawk,但这些并不是特定于 gawk 的,并且应该在任何 POSIX awk 中工作。
答案4
我会使用它perl
来代替gawk
它,这会使其更便携,更易于阅读且更可靠,并且允许使用-i
¹ 就地编辑文件:
perl -lpse 'substr($_, $offset, length($text)) = $text if /^\s{$spaces}/
' -- -offset=14 -text=WORDWRAP -spaces=75 your-file
(perl
的偏移量substr()
从 0 开始,因此是 14 而不是 15)。
¹ 最新版本gawk
带有inplace.awk
扩展,但是很难安全使用