我需要使用 bash 解析制表符分隔的 CSV 文件,检查记录的内容,如果记录满足特定条件,则将其添加到数组中。基本上,我想先从 CSV 文件中过滤出记录,然后再对它们进行处理。
我的想法是获取文件中的每一行,将每个字段放入一个数组中。然后我可以查看数组以查看记录是否满足某些条件(例如 field3="value" 等)。如果是,我将“重建”制表符分隔行并将其附加到新数组中。
这似乎失败的是我创建的行record
。它似乎是附加一个空格而不是制表符,因为稍后, 的大小与details
记录是用空格而不是制表符分隔的一样。
datafile=path/to/data.csv
records=()
header=$(head -n 1 $datafile)
IFS=$'\t' read -r -a fields <<< "$header"
while IFS=$'\t' read -r -a documents; do
# processing to determine if current row in csv file matches certain criteria
# if it does, the following will happen
for r in ${documents[@]}; do record+="$r"$'\t'; done #appending space instead?
records+="$record"
done < $datafile
for r in "${records[@]}"; do
IFS=$'\t' read -r -a details <<< "$r"
# size of details here is as if record is separated by spaces instead of tabs
for i in "${!fields[@]}" ; do
echo "${fields[i]}: ${details[i]}"
done
done
示例:如果此记录是进程:
Hello World [TAB] nice weather we are having today [TAB] do you agree?
的大小details
应该是 3,但我得到的是 11。为什么?
答案1
你的问题已被涵盖为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?。我将简要解释一下这里发生的事情。
罪魁祸首是for r in ${documents[@]}
。由于变量扩展未加引号,因此您使用“split+glob”操作:每个数组元素的值根据 的值拆分为单词IFS
,并且每个单词都被视为通配符模式。因为您只设置了IFS
持续时间read
(请参阅为什么如此频繁地使用“while IFS= read”,而不是“IFS=;”在阅读时..`?),IFS
此时的值为默认值,其中包含空格。此外,如果您有一个包含类似 的字段foo *
,您会看到当前目录中的文件名出现。解决方案是for r in "${documents[@]}"
,这是迭代数组的标准方法:双引号将其转换为直接变量取消引用,没有拆分和通配符,并且导致[@]
每个数组元素被放置在单独的字中。
虽然对整个脚本的设置IFS=$'\t'
似乎可以解决问题,但实际上它只解决了问题的一半:它不能防止${documents[@]}
.虽然您可以使用 关闭通配符set -f
,但使用双引号更清晰。
答案2
问题显然与 的多重声明有关IFS=$'\t'
。删除它们并仅进行一个声明似乎IFS
就解决了问题。
(尽管对于我的一生来说,我没有看到为什么这是一个问题。肯定有一个微妙的错字。)