我需要帮助使用 grep 从 Linux 系统上的文件中提取分区日期时间。
源文件是一个 XML,包含以下数据:
<item start="20231010073000 +0100" stop="20231010100000 +0100">...</item>
我需要提取完整的开始日期,但使用 grep 我无法得到完整的结果。我的代码:
for startDate in $(grep -Eo 'start="[0-9]{14} [\+|\-][0-9]{4}"' "$filepath" ); do
echo "$startDate"
done
我得到了两个不同的结果:
start="20231010073000
+0100"
我可以得到它如下:
start="20231010073000 +0100"
我尝试过\s
、[[:space:]]
和其他示例,但使用相同的解决方案。
我的代码似乎有错误,但我无法修复它!
我很感谢任何形式的帮助!
答案1
不要使用grep
或regex
解析HTML/XML
您不能也不能使用旨在处理原始文本行的工具来解析任何结构化文本(例如 XML/HTML)。如果需要处理 XML/HTML,请使用 XML/HTML 解析器。绝大多数语言都内置了对解析 XML 的支持,并且有专门的工具,例如xidel
、xmlstarlet
或者xmllint
如果您需要从命令行 shell 快速进行操作... 如果您无法访问适当的工具,切勿接受工作。
最先进的命令行工具是xidel
。语法比or更直观/现代(并且支持XPath3
其他工具受限制的情况XPath1
),请参阅:xmlstarlet
xmllint
xidel -e '//item/@start' -s file.xml
20231010073000 +0100
-e
用于XPath
e
表达-s
对于s
ilent(无状态信息)
查询语言XPath
在许多情况下对于解析 XML/HTML 非常有用。
XPath
教程:
https://developer.mozilla.org/en-US/docs/Web/XPath
http://www.w3schools.com/xpath/xpath_functions.asp
http://stackoverflow.com/tags/xpath/info
https://topswagcode.com/xpath/(互动XPath
游戏,当你有基础并想互动练习时)
答案2
问题出在你的循环中:默认情况下,它会分割$IFS
(因此,默认值为:任何、或字符$IFS
序列,并且它还会丢弃第一个和最后一个)space
tab
newline
有很多方法可以解决这个问题,例如:
while IFS= read -r StartDate; do
echo "$StartDate"
done < <(grep -Eo -- 'start="[0-9]{14} [+-][0-9]{4}"' "$filepath")
(我使用: loop < <( command generating the input )
形式而不是command generating the input | loop
形式:,以便循环位于当前 shell 中,而不是像未启用该选项时 bash shell 中的情况那样位于子 shell 中lastpipe
。这并不总是必要的,但对于例如,如果您想$StartDate
在循环后查看 的最新值:如果在子 shell 中,则该值将在子 shell 末尾消失,并且无法在当前 shell 中检索。)
答案3
由于您正在处理 XML,因此我们实际上应该使用 XML 解析器来获取属性的值。
下面展示了如何使用以下命令从整个文档中的start
任意节点获取属性值:item
xmlstarlet
$ xmlstarlet select --template --value-of '//item/@start' --nl file
20231010073000 +0100
或者,使用缩写选项名称:
$ xmlstarlet sel -t -v '//item/@start' -n file
20231010073000 +0100
如果有多个item
节点,并且您只需要start
第一个节点的属性值,请//item[1]/@start
在 XPath 查询中使用。
然后,您可以使用标准命令替换将结果传输到变量中:
start=$( xmlstarlet sel -t -v '//item[1]/@start' file )
(我-n
从上面的命令中删除了该选项,因为它不再需要。它在输出的末尾添加了一个换行符,但命令替换会删除它。)
bash
或者,您可以使用以下命令将它们全部读入数组readarray
:
readarray -t startarray < <(
xmlstarlet sel -t -v '//item/@start' -n file
)
然后循环它 ( ) 或直接for start in "${startarray[@]}"; do ...; done
循环它的输出:xmlstarlet
while IFS= read -r start; do
# ...
done < <( xmlstarlet ...as above... )
答案4
如果您无法在系统上安装额外的依赖项来正确解析 XML,那么我会编写一个脚本来更优雅地处理解析,而不是试图在一行中完成它。
这是我从您提供的行中解析出那些时间的示例脚本。
#!/usr/bin/env bash
INPUT_FILE="$1"
TIME_FILTER='[0-9]*\s(\+|\-)[0-9]*'
__getStart(){
line="$1"
echo "$line" | egrep -o "start=\"${TIME_FILTER}\"" | egrep -o "$TIME_FILTER"
}
__getStop(){
line="$1"
echo "$line" | egrep -o "stop=\"${TIME_FILTER}\"" | egrep -o "$TIME_FILTER"
}
while IFS= read -r line; do
start_time="$(__getStart "$line")"
stop_time="$(__getStop "$line")"
echo "Start Time: ${start_time}"
echo "Stop Time: ${stop_time}"
done < "$INPUT_FILE"
您可以以这种方式使用该脚本
[/var/tmp] $ ./get-dates.sh date-extraction.xml
Start Time: 20231010073000 +0100
Stop Time: 20231010100000 +0100