我有 7 个 csv 文件,其中有一些气候数据。文件的名称是:SMVV50065-2015-01.csv
和等*2015-02.csv
。2015-03.csv
当我打开 csv 文件时,我看到这样的语法:
" SMVV, 2015-01-01 00:00,50065,780,7,1000,-2,18, , ,1000"
指温度、压力、湿度等测量。 “、”为缺失数据。我使用 sed 命令将缺失值从间隙更改为 NA。更具体地说,我写了
sed 's/ ,/NA/g' SMVV50065-2015-01.csv > newfile01.csv
我设法将所有差距更改为 NA。问题是我想使用 foreach 命令对其余文件执行相同的操作,并在更改后将它们保存在具有名称的新文件中等。newfile01.csv
您newfile02.csv
知道该命令的确切语法是什么吗?
答案1
我假设您的 CSV 文件中严格不包含带逗号的引号,并且不包含带换行符的字段。
这会将空字段或仅包含空格的字段更改为NA
:
awk 'BEGIN { FS=OFS="," } { for (i=1;i<=NF;++i) if ($i ~ /^ *$/) $i = "NA"; print }'
对于每行输入上的每个逗号分隔字段,我们测试它是否与正则表达式匹配^ *$
。如果是,则该字段将替换为字符串NA
。块中的FS
和变量分别是输入和输出字段分隔符。是在当前输入行中检测到的字段数,如果是整数,则将是与该整数对应的字段,从 1 开始计数。OFS
BEGIN
NF
awk
i
$i
你的示例行,
SMVV, 2015-01-01 00:00,50065,780,7,1000,-2,18, , ,1000
会变成
SMVV, 2015-01-01 00:00,50065,780,7,1000,-2,18,NA,NA,1000
现在,要在所有文件上运行此命令,我假设它们都位于名为 的目录中dir
,并且文件名与模式匹配SMVV50065*.csv
。
循环这些文件的问题是
for name in dir/SMVV50065*.csv; do
test -f "$name" || continue
# construct new name and call awk here
done
我们测试test -f
它是否$name
实际上是常规文件,如果不是,则跳过迭代的其余部分。它会不是如果模式与任何目录名称匹配,或者模式不匹配,则为任何事物(在这种情况下,它将保持未展开状态)。
要按照您建议的模式构造新文件名,我们可以保留一个计数器变量,该变量从一次开始在每次迭代中递增,并printf
使用格式化字符串进行调用,该格式化字符串使用此变量给出输出文件名:
i=1
for name in dir/SMVV50065*.csv; do
test -f "$name" || continue
newname=$( printf 'newfile%02d.csv' "$i" )
i=$(( i + 1 ))
# call awk here
done
%02d
格式中的为printf
我们提供了来自 的 2 位零填充整数$i
。
现在只需调用awk
旧文件名并将结果写入新文件即可。我们将结果写入result
目录中的文件中,只是为了将它们与原始文件分开。
#!/bin/sh
mkdir -p result
i=1
for name in dir/SMVV50065*.csv; do
test -f "$name" || continue
newname=$( printf 'newfile%02d.csv' "$i" )
i=$(( i + 1 ))
awk 'BEGIN { FS=OFS="," } { for (i=1;i<=NF;++i) if ($i ~ /^ *$/) $i = "NA"; print }' "$name" >result/"$newname"
done
我在这里所做的唯一另一件事是确保该目录在开始时result
确实存在。mkdir -p result
我还在#!
顶部添加了一行来表示这是一个sh
脚本。
再次添加一些诊断和参数化:
#!/bin/sh
indir=dir
outdir=result
mkdir -p "$outdir"
i=1
for name in "$indir"/SMVV50065*.csv; do
if [ ! -f "$name" ]; then
printf 'Not a regular file: "%s"\n' "$name" >&2
continue
fi
newname=$( printf '%s/newfile%02d.csv' "$outdir" "$i" )
i=$(( i + 1 ))
printf 'Processing "%s" into "%s"...\n' "$name" "$newname" >&2
awk 'BEGIN { FS=OFS="," } { for (i=1;i<=NF;++i) if ($i ~ /^ *$/) $i = "NA"; print }' "$name" >"$newname"
done
如果你愿意的话,你显然可以把你的sed
命令放在这里代替我的东西。awk
评论中的问题:
上述看起来很难,为什么我们不能这样做
foreach file (ls SMVV50065-2015-0[1-7].csv)
sed 's/ ,/NA/g' > newfile0[1-7].csv
end
回复:
我们首先必须从使用正确的语法开始。这看起来有点像csh
shell 的语法,但由于问题中没有提到特定的 shell,并且sh
类似 shell 更常用,和csh
由于我个人对and 的经验很少tcsh
,所以我将把它转换成sh
语法。
sh
shell中的循环是for
whileforeach
而我们使用in
anddo
代替括号。您还建议使用ls
for 循环,但ls
严格来说是一个交互式命令,其结果是仅供观赏(看 ”为什么*不*解析`ls`?")。使用文件名通配模式就足以生成要循环的文件名列表。
因此,让我们以正确的语法使用循环:
for file in SMVV50065-2015-0[1-7].csv; do
sed 's/ ,/NA/g' > newfile0[1-7].csv
done
这里循环的下一个问题是我们根本不知道它是否$file
是一个有用的值。如果模式 SMVV50065-2015-0[1-7].csv
与目录名称匹配或者根本不匹配任何内容,那么我们不应该使用$file
,所以让我们测试一下:
for file in SMVV50065-2015-0[1-7].csv; do
test -f "$file" || continue
sed 's/ ,/NA/g' > newfile0[1-7].csv
done
现在进行sed
调用:您需要将文件名传递$file
给它,sed
以便它可以处理一些事情:
for file in SMVV50065-2015-0[1-7].csv; do
test -f "$file" || continue
sed 's/ ,/NA/g' "$file" > newfile0[1-7].csv
done
下一个问题是您实际上无法将输出重定向sed
到文件名通配模式,例如newfile0[1-7].csv
.通配模式将被 shell 扩展为与该模式匹配的所有名称,或者如果它不匹配任何内容,它将保持未扩展状态。
假设当前目录中没有与该newfile0[1-7].csv
模式匹配的文件。然后,循环将创建一个名为 的文件newfile0[1-7].csv
,并且该填充将在循环的每次迭代中被覆盖。
这就是我引入变量的原因i
,以便我可以在每次迭代中构造一个新的文件名:
i=1
for file in SMVV50065-2015-0[1-7].csv; do
test -f "$file" || continue
sed 's/ ,/NA/g' "$file" >"newfile0$i.csv"
i=$(( i + 1 ))
done
我假设您可能有远不止七个文件需要处理,这就是为什么我经历了一些额外的麻烦来使用 生成输出文件名printf
,以确保我们得到一个包含零填充数字的文件名。
上面的循环可能适合您,但如果我稍微重新编写它(将新文件名分配给变量并将其与 一起使用sed
):
i=1
for file in SMVV50065-2015-0[1-7].csv; do
test -f "$file" || continue
newname="newfile0$i.csv"
i=$(( i + 1 ))
sed 's/ ,/NA/g' "$file" >"$newfile"
done
你看?我们或多或少回到了我的解决方案(没有我最后一个变体的额外花哨)。唯一的根本区别是,您在这里假设所有文件都在当前目录中可用,并且输出文件应与原始文件一起创建。
答案2
下面是我尝试过的
filnames.txt==> 包含所有文件名
for j in `cat filenames.txt`; do sed "s/ ,/NA/g" $j >newfiles_$i;i=$(($i + 1)); done