UNIX foreach 和 sed 命令!

UNIX foreach 和 sed 命令!

我有 7 个 csv 文件,其中有一些气候数据。文件的名称是:SMVV50065-2015-01.csv和等*2015-02.csv2015-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.csvnewfile02.csv知道该命令的确切语法是什么吗?

答案1

我假设您的 CSV 文件中严格不包含带逗号的引号,并且不包含带换行符的字段。

这会将空字段或仅包含空格的字段更改为NA

awk 'BEGIN { FS=OFS="," } { for (i=1;i<=NF;++i) if ($i ~ /^ *$/) $i = "NA"; print }'

对于每行输入上的每个逗号分隔字段,我们测试它是否与正则表达式匹配^ *$。如果是,则该字段将替换为字符串NA。块中的FS和变量分别是输入和输出字段分隔符。是在当前输入行中检测到的字段数,如果是整数,则将是与该整数对应的字段,从 1 开始计数。OFSBEGINNFawki$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 

回复:

我们首先必须从使用正确的语法开始。这看起来有点像cshshell 的语法,但由于问题中没有提到特定的 shell,并且sh类似 shell 更常用,csh由于我个人对and 的经验很少tcsh,所以我将把它转换成sh语法。

shshell中的循环是forwhileforeach而我们使用inanddo代替括号。您还建议使用lsfor 循环,但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

相关内容