我一直在研究将多个 csv 文件连接成一个大的 csv 的脚本。 csv 包含文件夹的名称及其各自的大小,采用 2 列设置,格式为“大小,项目名称”
单个 csv 文件的示例:
49747851728,ODIN
32872934580,_WORK
9721820722,LIBRARY
4855839655,BASELIGHT
1035732096,ARCHIVE
907756578,USERS
123685100,ENV
3682821,SHOTGUN
1879186,SALT
361558,SOFTWARE
486,VFX
128,DNA
对于我当前的测试,我有 25 个相似的文件,第一列中的数字不同。
我正在尝试让这个脚本执行以下操作:
- 读取每个csv文件
- 对于它看到的每个项目,如果该项目已经打印到文件中,则扫描输出文件。如果不是,打印项目名称
- 对于每个文件,对于每个项目,如果找到该项目,则将大小打印到输出 csv。
但是,我需要所有项目都在文本行 1 上,以逗号分隔,这样我就可以将此输出文件用作 javascript 图形的输入。尺寸应添加到其项目名称下方的列中。
我当前的脚本:
csv_folder=$(echo "$1" | sed 's/^[ \t]*//;s/\/[ \t]*$//')
csv_allfiles="$csv_folder/*.csv"
csv_outputfile=$csv_folder.csv
echo -n "" > $csv_outputfile
for csv_inputfile in $csv_allfiles; do
while read line && [[ $line != "" ]]; do
projectname=$(echo $line | sed 's/^\([^,]*\),//')
projectfound1=$(cat $csv_outputfile | grep -w $projectname)
if [[ ! $projectfound1 ]]; then
textline=1
sed "${textline}s/$/${projectname}, /" >> $csv_outputfile
for csv_foundfile in $csv_allfiles; do
textline=$(echo $textline + 1 | bc )
projectfound2=$(cat $csv_foundfile | grep -w $projectname)
projectdata=$(echo $projectfound2 | sed 's/\,.*$//')
if [[ $projectfound2 ]]; then
sed "${textline}s/$/$projectdata, /" >> $csv_outputfile
fi
done
fi
done < $csv_inputfile
done
我当前的脚本找到了正确的信息(项目名称、项目数据),如果我只是“回显”这些变量,它会将正确的数据打印到文件中。但是,对于 echo,它只会在每个项目中打印一个长列表。我希望它“跳回”到第 1 行并在当前行的末尾打印新项目,然后运行循环以在下一行的末尾打印数据。
我认为这应该可以通过 sed 或 awk 实现。 sed 应该有办法用
将文本插入特定行sed '{n}s/search/replace/'
其中 {n} 是要插入的行
awk 应该能够用类似的东西做同样的事情
awk -v l2="$textline" -v d="$projectdata" 'NR == l2 {print d} {print}' >> $csv_outputfile
但是,将脚本中的 sed 命令替换为
echo $projectname
echo $projectdata
吐出正确的信息(所以我知道我的变量被正确填充)sed 和 awk 命令倾向于吐出它们当前 inputcsv 的全部内容;不仅仅是我希望他们使用的那一行。
Pastebin 输出每种写入文件的变体
- https://pastebin.com/XwxiAqvT - sed 输出
- https://pastebin.com/xfLU6wri - echo ,纯输出(单列)
- https://pastebin.com/wP3BhgY8 - echo,每个变量的详细输出
- https://pastebin.com/5wiuq53n - 期望的输出
如您所见,sed 输出倾向于粘贴 inputcsv 的全部内容,使循环在一次迭代后停止。 (因为它在一个循环后找到了其他项目)
所以我的问题是其中之一;
- 如何让 sed/awk 以我想要的方式运行;即仅将我的 var 中的信息打印到当前文本行,而不是整个输入 csv。 sed 是否能够做到这一点,只打印一行变量?或者
- 我是否应该通过“echo”将变量输出到一个临时文件中,然后遍历该临时文件以使 sed 按我希望的方式对行进行排序? (请记住,将来会添加更多 .csv 文件,我不能让它循环 x 次来对信息进行排序)
- 有没有办法在不使用 sed 或 awk 的情况下将文本回显/打印到特定文本行?我缺少 printf 选项吗?其他想法?
非常感谢任何帮助。
最佳答案
完成这种转置的一种方法是将数据保存到关联数组。
在下面的示例中,我们使用二维数组来跟踪我们的数据。因为排序似乎很重要,所以我们创建了一个 col 数组,每当我们看到一个新的 projectname 时就创建一个新的增量——这个 col 数组最终成为我们数据的第一个索引。我们还创建了一个行数组,只要我们看到当前列的新数据,我们就会递增该数组。行号是我们对数据的第二个索引。最后,我们打印出所有的记录。
#! /usr/bin/awk -f
BEGIN {
FS = ","
OFS = ", "
rows=0
cols=0
head=""
split("", data)
split("", row)
split("", col)
}
!($2 in col) { # new project
if (head == "")
head = $2
else
head = head OFS $2
i = col[$2] = cols++
row[i] = 0
}
{
i = col[$2]
j = row[i]++
data[i,j] = $1
if (j > rows)
rows = j
}
END {
print head
for (j=0; j<=rows; ++j) {
if ((0,j) in data)
x = data[0,j]
else
x = ""
for (i=1; i<cols; ++i) {
if ((i,j) in data)
x = x OFS data[i,j]
else
x = x OFS
}
print x
}
}
作为奖励,这里有一个脚本可以从您的一个 pastebin 中重现详细的输出。
#! /usr/bin/awk -f
BEGIN {
FS = ","
split("", data) # accumulated data for a project
split("", line) # keep track of textline for data
split("", idx) # index into above to maintain input order
sz = 0
}
$2 in idx { # have seen this projectname
i = idx[$2]
x = ORS "textline = " ++line[i]
x = x ORS "textdata = " $1
data[i] = data[i] x
next
}
{ # new projectname
i = sz++
idx[$2] = i
x = "textline = 1"
x = x ORS "projectname = " $2
x = x ORS "textline = 2"
x = x ORS "projectdata = " $1
data[i] = x
line[i] = 2
}
END {
for (i=0; i<sz; ++i)
print data[i]
}
关于bash - 使用 sed/awk 将变量的内容打印到输出文件中的特定行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49388976/