我想查找目录中 4 个 CSV 文件中每个文件的日期范围。日期列是每个 csv 中的最后一列,格式为1/25/2012 7:20:55 PM
.
日期字段始终是最后一列,即第 24 列。仅每个 csv 中的记录发生变化。并且日期已定。
有没有一种方法可以让我对每个文件执行此操作并获得最终范围(假设日期已排序)?
因此,如果我的第一条记录是 1/25/2012 7:20:55 PM,最后一条记录是 11/7/2016 2:36:20 PM。
我希望输出日期范围为 1/25/2012 - 11/7/2016。但我想将所有 4 个文件的日期范围合并在一起。
输入示例(为简洁起见,省略了一些列):
第一个文件:
第1栏,第2栏,第3栏,...,第 23栏,col24_时间 值1,值2,值3,...,值23,1/25/2012 7:20 下午 值1,值2,值3,...,值23,1/26/2012 10:57 上午 值1,值2,值3,...,值23,1/26/2012 2:20 下午 值1,值2,值3,...,值23,1/30/2012 11:55 上午 值1,值2,值3,...,值23,2012 年 1 月 30 日下午 3:17 值1,值2,值3,...,值23,1/30/2012 5:36 下午 值1,值2,值3,...,值23,1/30/2012 8:16 下午 ... 值1,值2,值3,...,值23,4/11/2012 11:45 上午 值1,值2,值3,...,值23,4/11/2012 2:23 下午
中间文件
最后一个文件:
值1,值2,值3,...,值23,3/11/2015 4:45 上午 值1,值2,值3,...,值23,3/11/2015 上午 8:40 ... 值1,值2,值3,...,值23,11/7/2016 2:36 下午
我的每个文件中有近 5-10K 条记录。日期在文件中按顺序排列。每个文件的每一列都有一个标题。
该命令的输出head -n7 Files/file1.csv | cut -d, -f24
是:
"col24_time"
"2012-01-01 00:30:26"
"0"
"2012-01-01 02:00:37"
"0"
"0"
"https://external.xx.fbcdn.net/safe_image.php?" <<-- previous column record?
答案1
我仍然不确定我是否理解这个问题。但这里有一些代码可以根据指定的输入生成所需的输出,并且它比其他答案短得多:
datetime1=$(head -n1 file1.csv | cut -d, -f24)
datetime4=$(tail -n1 file4.csv | cut -d, -f24)
printf '%s - %s\n' "${datetime1%% *}" "${datetime4%% *}"
这将从第一个文件中获取第一行,并从第四个文件和最后一个文件中获取最后一行,并提取第 24 个字段(基于,
作为每个的分隔符)。这些是日期时间字符串;具体来说,1/25/2012 7:20 AM
并且11/7/2016 2:36 PM
。然后,它通过去掉第一个空格及其后面的所有内容来打印每个单词的第一个“单词”。这些是所需的日期。
这是一个准单行等价物。为了便于阅读,我将其分成三行,但从逻辑上讲,它是一个很长的命令。
printf '%s - %s\n' \
"$(head -n1 file1.csv | cut -d, -f24 | cut -d' ' -f1)" \
"$(tail -n1 file4.csv | cut -d, -f24 | cut -d' ' -f1)"
在这里,由于我们没有使用任何变量,因此无法使用参数扩展,因此我用第二个 . 提取了第 24 个字段的第一个“单词” cut
。
答案2
如果您的数据,正如您所说,已经按日期排序且结构一致,那么您可以使用它sed
来处理您的特定行:
sed -E -n "2 {s/.*,([^ ]*).*/\1 - /;h}; $ {s/.*,([^ ]*).*/\1/;H;x;s/\n//;p}" file
第一个文件的输出是
1/26/2012 - 4/11/2012
将所有文件放在一起cat
(假设按日期顺序命名并按正确的顺序进行管道传输):
cat file* | sed ...
1/26/2012 - 11/7/2016
演练
设置sed
默认为-n
ot 打印
sed -E -n "
抓住第2
nd 行并收集您想要的行部分([^ ]+)
作为捕获组,并在模式空间中将输出组合为捕获和分隔符\1 -
2 {s/.*,([^ ]+).*/\1 - /;
将其推入h
旧空间(h
清除之前存在的任何内容)
h};
在纬度$
线上,在图案空间中再次抓取您想要的线条部分
$ {s/.*,([^ ]+).*/\1/;
将新模式空间附加到旧空间,并在新旧内容之间H
添加一条ewline(添加ewline),然后使用模式空间更改保留空间的全部内容\n
H
\n
x
H;x;
现在您的组合输出位于模式空间中,只需删除不需要的\n
ewline 和p
rint
s/\n//;p}" file
答案3
如果你喜欢 Unix 管道,你可以这样做:
# standalone example: this converts from a 2-colum, 1-line "csv" to unixtime,
# and converts back to readable date
echo "2,1/25/2012 7:20:55 PM" \
| perl -aF, -MDate::Parse -E "say Date::Parse::str2time(\$F[1])" - \
| xargs -i date "+%D " -d@{}
# result
01/25/12
这依赖于旧的但非核心的 Perl 模块Date::Parse
,如果您还没有它,您需要先安装它。
也许使用cpan Date::Parse
或零配置客户端安装cpanm Date::Parse
。
因此,对于您的示例,您可以尝试在两条单行上获取最年轻和最年长的数据
perl -aF, -MDate::Parse -E "say Date::Parse::str2time(\$F[5])" *.csv \
| sort \
| sed -e 1b -e '$!d' \
| xargs -i date "+%D " -d@{}
# result
01/25/12
11/07/16
该sed
线路是从这个帖子在这个网站上。
答案4
下面的awk
程序将会运行(我们称之为timerange.awk
)。它的设计使您不必以任何特定顺序提供文件,而可以简单地使用它*.csv
- 否则,您只需要提供目录中的第一个和最后一个文件,因为您声明时间戳是有序的。
#!/usr/bin/awk -f
# For every line of the files (after the first, which contains headers)
FNR>1{
# Break the time stamp field into its individual components and reassemble
# in a way that 'mktime' understands, to generate an epoch-based timestamp
# for "later/earlier than"-type comparisons.
split($NF,a,/[ /:]/);
if (a[6]=="AM" && a[4]==12) a[4]=0;
if (a[6]=="PM") a[4]=a[4]+12;
tst=a[3]" " a[1] " " a[2] " " a[4] " " a[5] " 00";
curr_ts=mktime(tst);
# If we are on the first "data" row of the first file, initialize start and end
# date
if (NR==2)
{
end=start=$NF;
end_ts=start_ts=curr_ts;
}
# On all later lines, check if the timestamps associated with the "start"
# and "end" time specifications are later resp. earlier than that of the
# current line. If so, update "start" and "end" specifications.
else
{
if (curr_ts>end_ts) {end_ts=curr_ts; end=$NF};
if (curr_ts<start_ts) {start_ts=curr_ts; start=$NF};
}
}
# After the last file was processed: Output the human-readable range
END{print start " - " end}
你可以将其称为
awk -F, -f timerange.awk file1.csv file2.csv ...
或者简单地
awk -F, -f timerange.awk *.csv
因为文件的顺序不相关;它将自动查找全局“第一个”和“最后一个”条目。
示例输入的输出(以其当前形式 - 我假设与您的第一个语句相反,时间戳确实不是包括秒):
1/25/2012 10:57 AM - 11/7/2016 2:36 PM
更新
如果你想完全忽略一天中的时间,可以删减该程序:
#!/usr/bin/awk -f
# For every line of the files (after the first, which contains headers)
FNR>1{
split($NF,a,/[ /:]/);
tst=a[3]" " a[1] " " a[2] " 00 00 00"
curr_ts=mktime(tst);
sub(/[[:space:]]+.* [AP]M$/,"",$NF);
if (NR==2)
{
end=start=$NF;
end_ts=start_ts=curr_ts;
}
else
{
if (curr_ts>end_ts) {end_ts=curr_ts; end=$NF};
if (curr_ts<start_ts) {start_ts=curr_ts; start=$NF};
}
}
END{print start " - " end}