情况:csv 文件具有不同日期格式的日期列,我想将它们转换为明确的日期格式(如 +"%m-%d-%Y")。
文件数据示例:文件名 = Date_Test_new.csv
3/29/2019, Test, "I am new to, Unix", 04-05-2023
03/29/19, Test, "I am new to, Unix", 04-5-2023
Apr-29-2019, Test, "I am new to, Unix", "Apr-01-2019"
3/29/2019, Test, "I am new to, Unix", "Apr-01-2019"
**Source Date formats**
DD-MMM-YY -> 08-Sep-23
DD-MMM-YYYY -> 08-Sep-2023
MM/DD/YY -> 09/08/23
MM/DD/YYYY -> 09/08/2023
MM-DD-YYYY -> 09-08-2023
YYYYMMDD -> 20230908
DDMMMYY -> 08Sep23
所需输出
03-29-2019,Test, "I am new to, Unix", 04-05-2023
03-29-2019, Test, "I am new to, Unix", 04-05-2023
04-29-2019, Test, "I am new to, Unix", 04-01-2019
03-29-2019, Test, "I am new to, Unix", 04-01-2019
我尝试过但抛出语法错误:
awk -F ',' '$1="date -d "$1" +"%m-%d-%Y" " ' Date_Test_new.csv > New_Output.csv
简而言之,如何将文件中一整列的日期格式转换为不同的日期格式?
任何帮助对我来说都是一个很好的学习。谢谢
答案1
假设您的真实数据采用有效的 CSV 格式,即字段分隔逗号后没有空格,然后使用 GNU awk for FPAT
、gensub()
和 3rg arg 来match()
:
$ cat tst.awk
BEGIN {
split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec",tmp)
for (i in tmp) {
mths[tmp[i]] = i
}
FPAT = "[^,]*|(\"([^\"]|\"\")*\")"
OFS = ","
}
{
for ( i=1; i<=NF; i++ ) {
val = gensub(/^"|"$/,"","g",$i)
day = mth = yr = 0
if ( match(val,/^([0-9]{1,2})-([[:alpha:]]{3})-([0-9]{2}|[0-9]{4})$/,d) ) {
# D-MMM-YY or DD-MMM-YY or ...YYYY
day = d[1]
mth = mths[d[2]]
yr = d[3]
}
else if ( match(val,/^([[:alpha:]]{3})-([0-9]{1,2})-([0-9]{2}|[0-9]{4})$/,d) ) {
# MMM-D-YY or MMM-DD-YY or ...YYYY
day = d[2]
mth = mths[d[1]]
yr = d[3]
}
else if ( match(val,/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{2}|[0-9]{4})$/,d) ) {
# M/D/YY or M/DD/YY or MM/D/YY or MM/DD/YY or ...YYYY
day = d[2]
mth = d[1]
yr = d[3]
}
else if ( match(val,/^([0-9]{1,2})-([0-9]{1,2})-([0-9]{2}|[0-9]{4})$/,d) ) {
# M-D-YY or M-DD-YY or MM-D-YY or MM-DD-YY or ...YYYY
day = d[2]
mth = d[1]
yr = d[3]
}
else if ( match(val,/^([0-9]{2})([[:alpha:]]{3})([0-9]{2})$/,d) ) {
# DDMMMYY
day = d[1]
mth = mths[d[2]]
yr = d[1]
}
else if ( match(val,/^([0-9]{4})([0-9]{2})([0-9]{2})$/,d) ) {
# YYYYMMDD
day = d[3]
mth = d[2]
yr = d[1]
}
if ( length(yr) == 2 ) {
yr = "20" yr
}
day += 0
mth += 0
yr += 0
if ( (1 <= day) && (day <= 31) &&
(1 <= mth) && (mth <= 12) &&
(1 <= yr) && (yr <= 9999) ) {
$i = sprintf("%04d-%02d-%02d", yr, mth, day)
}
}
print
}
$ awk -f tst.awk Date_Test_new.csv
2019-03-29,Test,"I am new to, Unix",2023-04-05
2019-03-29,Test,"I am new to, Unix",2023-04-05
2019-04-29,Test,"I am new to, Unix",2019-04-01
2019-03-29,Test,"I am new to, Unix",2019-04-01
我想我涵盖了您列出的每个日期格式以及更多内容,但只需为else if ( match(...) ) { ... }
您希望能够解析的任何其他格式添加块即可。
您还可以match()
通过放宽一些正则表达式来合并一些更相似的调用,或者使一些正则表达式更加严格(如果这对您来说更有效)。
更改sprintf()
为您喜欢的任何输出格式,但我建议您坚持使用ISO 8601 日期格式, YYYY-MM-DD
, 我使用它是为了便于随后处理这些日期。
如果您愿意,可以随意添加或更改有效日期的任何检查。由于我们使用的是 GNU awk,如果您愿意,您可以记住每个块中的输入格式,然后在底部使用mktime()
将新创建的日期转换为自纪元以来的秒数,然后strftime()
将这些秒转换回原始格式,看看它是否与原始日期相同,以便万无一失地验证您是否准确匹配和转换。留下作为练习......:-)。
如果您的数据在字段分隔后确实有空格,
:
$ cat file
3/29/2019, Test, "I am new to, Unix", 04-05-2023
03/29/19, Test, "I am new to, Unix", 04-5-2023
Apr-29-2019, Test, "I am new to, Unix", "Apr-01-2019"
3/29/2019, Test, "I am new to, Unix", "Apr-01-2019"
您可以使用以下命令将其转换为有效的 CSV:
$ awk 'BEGIN{FS=OFS="\""} {for (i=1; i<=NF; i+=2) gsub(/, /,",",$i)} 1' file
3/29/2019,Test,"I am new to, Unix",04-05-2023
03/29/19,Test,"I am new to, Unix",04-5-2023
Apr-29-2019,Test,"I am new to, Unix","Apr-01-2019"
3/29/2019,Test,"I am new to, Unix","Apr-01-2019"
上述所有内容都假设您的字段不能包含换行符。如果字段包含换行符,则需要更多工作。有关使用 awk 处理 CSV 的更多信息,请参阅使用 awk 高效解析 csv 的最稳健方法是什么。