一个更短的、符合POSIX 的awk 解决方案,它是@Tiago's excellent Perl-based answer 的通用和优化转换。
与sed 解决方案相比,这些答案的一个优点是它们使用 literal substring 匹配而不是正则表达式,这允许传入任意搜索字符串,而无需担心转义.也就是说,如果您确实想要正则表达式匹配,请使用~ 运算符而不是index() 函数;例如,index($0, name) 将变为 $0 ~ name。然后,您必须确保为 name 传递的值不包含意在被视为文字的意外正则表达式元字符或是故意制作的正则表达式。
name='DOG' # Case-sensitive name to search for.
awk -v name="$name" '/^[^[:space:]]/ {if (p) exit; if (index($0,name)) {p=1}} p' file
-
选项
-v name="$name"定义awk变量name基于shell变量$name的值(awk没有直接访问 shell 变量)。
-
变量
p 用作一个标志来指示当前行是否应该被打印,即它是否是感兴趣的部分的一部分;只要p 未初始化,它就会在布尔上下文中被视为0 (false)。
-
模式
/^[^[:space:]]/ 仅匹配标题行(以非空白字符开头的行),并且仅为它们处理相关的动作 ({...}):
-
if (p) exit 完全退出处理,如果 p 已经设置,因为这意味着已经到达 next 部分。立即退出的好处是不必处理文件的其余部分。
-
if (index($0, name)) 在手头的标题行中查找感兴趣的名称作为 文字子字符串,如果找到(在这种情况下 index() returns the 1-based position at which the substring was found, which is interpreted astruein a Boolean context), sets flagpto1( {p=1}`)。
-
p 仅打印当前行,如果 p 是 1,否则不执行任何操作。也就是说,一旦找到感兴趣的部分标题,就会打印它和后续行(直到下一个部分或输入文件的末尾)。
请注意,这是一个仅模式命令的示例:仅指定了一个模式(条件),没有关联的操作 ({...}),在这种情况下,默认操作是打印当前线,如果模式评估为真。 (该技术在常用速记 1 中用于简单地无条件打印当前记录。)
如果需要不区分大小写:
name='dog' # Case-INsensitive name to search for.
awk -v name="$name" \
'/^[^[:space:]]/ {if(p) exit; if(index(tolower($0),tolower(name))) {p=1}} p' file
警告:macOS 附带的基于 BSD 的 awk(自 10.12.1 起仍适用)不支持 UTF-8。 : 不区分大小写的匹配不适用于ü等非ASCII字母。
GNU awk 替代,使用特殊的 IGNORECASE 变量:
awk -v name="$name" -v IGNORECASE=1 \
'/^[^[:space:]]/ {if(p) exit; if(index($0,name)) {p=1}} p' file
另一个符合 POSIX 的awk 解决方案:
name='dog' # Case-insensitive name of section to extract.
awk -v name="$name" '
index(tolower($0),tolower(name)) {inBlock=1; print; next} # 1st section line found.
inBlock && !/^[[:space:]]/ {exit} # Exit at start of next section.
inBlock # Print 2nd, 3rd, ... section line.
' file
注意:
-
next 跳过剩余的模式-动作对并继续下一行。
-
/^[[:space:]]/ 匹配以 至少一个 空白字符开头的行。正如@Chrono Kitsune 在他的回答中解释的那样,如果您想匹配以 exactly one 空格字符开头的行,请使用/^[[:space:]][^[:space:]]/。另请注意,尽管它的名称,字符类 [:space:] 匹配任何形式的空白,而不仅仅是空格 - 请参阅 man isspace。
- 无需初始化标志变量
inBlock,因为它在数字/布尔上下文中默认为0。
- 如果您有 GNU
awk,您可以通过将 IGNORECASE 变量设置为非零值 (-v IGNORECASE=1) 并简单地在内部使用 index($0, name) 来更轻松地实现不区分大小写的匹配程序。
一个 GNU awk 解决方案,如果,您可以假设所有节标题行都以 'ip' 开头(以便以这种方式将输入分成多个部分,而不是寻找前导空格):
awk -v RS='(^|\n)ip' -F'\n' -v name="$name" -v IGNORECASE=1 '
index($1, name) { sub(/\n$/, ""); print "ip" $0; exit }
' file
-
-v RS='(^|\n)ip' 按位于字符串 'ip' 的行起始实例之间的行将输入分成记录。
-
-F'\n' 然后将每条记录按行分成字段($1,...)。
-
index($1, name) 在当前记录的 first 行查找名称 - 不区分大小写,感谢 -v IGNORECASE=1。
-
sub(/\n$/, "") 删除任何尾随 \n,这可能源于感兴趣的部分是输入文件中的最后一个。
-
print "ip" $0 打印匹配的记录,包括整个感兴趣的部分 - 因为,但是记录不包括 分隔符,'ip',它是前置的。