我有多个 csv 文件,内容格式如下:
"TIMESTAMP",col2,col3,col4
"yyyy-mm-dd HH:mm",20,19,17
我想替换-
为/
,以便最终我有:
TIMESTAMP,col2,col3,col4
yyyy/mm/dd HH:mm,20,19,17
我使用以下命令附加文件,但没有任何反应:
find -name '*.csv' -exec awk '{gsub(/-/, "/",$1)}' '{}' \;
我缺少什么?请帮忙
答案1
find . -name '*.csv' -type f -size +10c -exec perl -pi -e '
s{^(\d\d\d\d)-(\d\d)-(\d\d)\b}{$1/$2/$3}' {} +
只会替换-
行开头的时间戳中的 s,而忽略所有其他-
事件。
请注意,它会替换所有.csv
文件,甚至那些不包含任何此类时间戳的文件。为了避免这种情况,使用 GNU grep
,您可以执行以下操作:
grep -rlPZ --include='*.csv' '^\d\d\d\d-\d\d-\d\d\b' . |
xargs -r0 perl -pi -e '
s{^(\d\d\d\d)-(\d\d)-(\d\d)\b}{$1/$2/$3}' {} +
答案2
您的awk
命令实际上确实进行了您想要的更改(除了它在第一个空格分隔字段而不是逗号分隔字段中进行更改),但它不会打印它,因为您没有告诉它打印:
$ cat file.csv
TIMESTAMP,col2,col3,col-4
yyyy-mm-dd HH:mm,20,19,17
$ awk '{gsub(/-/, "/",$1)}' file.csv
$
正如您在上面看到的,您的命令没有输出。与之比较:
$ awk -F, -v OFS=, '{gsub(/-/, "/", $1); print}' file.csv
TIMESTAMP,col2,col3,col-4
yyyy/mm/dd HH:mm,20,19,17
但是,这不太可能是您想要的,因为这只会将所有文件的内容打印到标准输出。如果您想修改实际文件,请尝试以下操作:
$ perl -i -F, -lane '$F[0] =~ s|-|/|g; print join ",",@F' file.csv
$ cat file.csv
TIMESTAMP,col2,col3,col4
yyyy/mm/dd HH:mm,20,19,17
我建议您使用-i.bak
它将创建带有扩展名的原始文件的副本.bak
,以防万一。然后,您可以将其合并到您的命令中,如下所示:
find . -name '*.csv' -type f -exec perl -i.bak -F, -lane '
$F[0] =~ y|-|/|; print join ",", @F' '{}' +
或者,如果您有 GNU awk ( gawk
) 并且可以保证当前工作目录不包含名为infile
或 的文件infile.awk
,您可以这样做:
find . -name '*.csv' -type f -exec gawk -F, -v OFS=, -i inplace '
{gsub(/-/, "/",$1); print}' '{}' +
答案3
将问题中显示的示例输入和预期输出与您所说的合并一条评论关于时间戳值的实际情况:
$ cat foo.csv
"TIMESTAMP",col2,col3,col4
""yyyy-mm-dd HH:mm"",20,19,17
然后使用 GNU awk 进行-i inplace
:
$ find . -name 'foo.csv' -exec awk -i inplace '{gsub(/"/,""); gsub(/-/,"/"); print}' {} +
$ cat foo.csv
TIMESTAMP,col2,col3,col4
yyyy/mm/dd HH:mm,20,19,17
或者您可以用此 GNU sed 脚本替换 GNU awk 脚本(对于-i
):
sed -i 's:"::g; s:-:/:g'
如果您不想在其他任何地方替换"
s 或-
s,并且引用字段内没有任何"
s、,
s 或换行符,则将 awk 脚本更改为:
BEGIN{FS=OFS=","} {gsub(/"/,"",$1); gsub(/-/,"/",$1); print}
答案4
使用乐(以前称为 Perl_6)
简单的方法:
~$ raku -pe 's:g{ \w**4 <( (\-) (\w**2) (\-) )> \w**2 } = "/$1/";' file
验证 CSV 内容:
~$ raku -MText::CSV -e 'my @a = csv(in => $*IN, sep => ","); \
@a>>.[0] = @a>>.[0].map: \
*.subst(:global, / \w**4 <( (\-) (\w**2) (\-) )> \w**2 /, {"/$1/"} ); \
csv(in => @a, out => $*OUT, sep => ",");' < file
以下是用 Raku(Perl 编程语言家族的成员)编写的答案。第一个答案使用熟悉的s///
替换惯用语,但这里 Raku 添加了一个新的格式选项:s{original} = "replacement"
。寻找字符/数字和破折号(连字符)的正确组合的匹配,Raku 的<(…)>
捕获标记用于删除所需块之外的所有内容,这些块在替换中会被更改。
第二个答案使用 Raku 的Text::CSV
模块验证 CSV 输入/输出。仅使用示例数据第一列被替换(该@a>>.[0]
习惯用法专门针对第一列数据)。
输入示例:
"TIMESTAMP",col2,col3,col4
"yyyy-mm-dd HH:mm",20,19,17
示例输出(第一个代码示例):
"TIMESTAMP",col2,col3,col4
"yyyy/mm/dd HH:mm",20,19,17
示例输出(第二个代码示例,验证 CSV 输出):
TIMESTAMP,col2,col3,col4
"yyyy/mm/dd HH:mm",20,19,17
Text::CSV
如果您需要更改此设置,Raku 的模块有多种用于引用输出列的选项(默认是引用包含空格的按列元素)。您还可以在代码主体中获取输入,而无需依赖 shell 通配。只需将csv(in => $*IN, sep => ",");
代码部分更改为:
csv(in => "path/to/file", sep => ",");
https://raku.land/zef:Tux/Text::CSV
https://docs.raku.org/routine/dir
https://raku.org