Bash 如果 csv 文件第一行的值不存在,如何用引号引起来
Bash How to wrap values of the first line of a csv file with quotations, if they do not exist
前几天我问如何用引号将 csv 文件第一行的值包装起来。我得到了这个回复,效果很好。
$ cat file.csv
word1,word2,word3,word4,word5
12345,12346,12347,12348,12349
仅在第一行中的项目周围加上引号:
$ sed '1 { s/^/"/; s/,/","/g; s/$/"/ }' file.csv
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
我现在需要测试值周围是否存在引号以消除双引号值的可能性。
更改每个替换以包括可选引号:
sed -E '1 { s/^"?/"/; s/"?,"?/","/g; s/"?$/"/ }' file.csv
我添加了 -E
以启用扩展模式,因此 ?
可以理解为“0 或 1 个匹配”。
您也可以继续使用基本模式(没有 -E
)并将每个 ?
替换为 \{0,1\}
(同样,0 或 1 个匹配项)或 *
(匹配 0 个或多个)。
这个问题比 sed
更适合 awk,因为 row/column 处理:
awk 'BEGIN{FS=OFS=","} NR==1 {
for (i=1; i<=NF; i++) {gsub(/^"|"$/, "", $i); $i = "\"" $i "\""}
} 1' file
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
- 使用
gsub
函数,我们删除前导或尾随双引号(如果存在)
- 然后我们可以安全地将每个单元格用双引号括起来
通过先删除所有可能的双引号来保留现有的有效 sed 命令:
sed '1 { s/"//g; s/^/"/; s/,/","/g; s/$/"/ }' file.csv
为了测试每个答案,我创建了三个文件:
file.csv
word1,word2,word3,word4,word5
12345,12346,12347,12348,12349
file2.csv
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
file3.csv
"word1",word2,word3,"word4",word5
12345,12346,12347,12348,12349
然后我创建了一个bash脚本
#!/bin/bash
sed -E '1 { s/^"?/"/; s/"?,"?/","/g; s/"?$/"/ }' file.csv > final.csv
sed -E '1 { s/^"?/"/; s/"?,"?/","/g; s/"?$/"/ }' file2.csv > final2.csv
sed -E '1 { s/^"?/"/; s/"?,"?/","/g; s/"?$/"/ }' file3.csv > final3.csv
然后我查看了最终文件,第一行是完美的。
# cat final*.csv
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
带有 sed
和 awk
的正则表达式会受到一系列看似永无止境的失败边缘情况的影响。相反,利用 csv 库提供了更多的稳健性。
我发现 Python 的图书馆是最佳选择,因为它:
- 除Python本身外,广泛可用,没有繁重的依赖关系;
- 对您使用的 Python 版本不是特别敏感;
- 适合嵌入到 shell 脚本中;和
- 非常紧凑(单线就可以!)。
因此,我的解决方案是:
QUOTE_CSV_PY='import sys; import csv; csv.writer(sys.stdout, quoting=csv.QUOTE_ALL).writerows(csv.reader(sys.stdin))'
head -1 file.csv | python -c "$QUOTE_CSV_PY"; tail -n +2 file.csv
分解:
QUOTE_CSV_PY
是一个包含 Python 单行命令的 shell 变量
- Python 命令只导入标准的
sys
和 csv
模块。然后它创建一个 csv 编写器,写入 stdout
并设置 QUOTE_ALL
以便所有字段都被引用。它被馈送一个 csv reader 从 stdin
. 读取
head -1
将第一行发送到 python 解释器进行处理。
; tail -n +2
等到处理完成,然后转储从第二行开始的每一行。
前几天我问如何用引号将 csv 文件第一行的值包装起来。我得到了这个回复,效果很好。
$ cat file.csv
word1,word2,word3,word4,word5
12345,12346,12347,12348,12349
仅在第一行中的项目周围加上引号:
$ sed '1 { s/^/"/; s/,/","/g; s/$/"/ }' file.csv
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
我现在需要测试值周围是否存在引号以消除双引号值的可能性。
更改每个替换以包括可选引号:
sed -E '1 { s/^"?/"/; s/"?,"?/","/g; s/"?$/"/ }' file.csv
我添加了 -E
以启用扩展模式,因此 ?
可以理解为“0 或 1 个匹配”。
您也可以继续使用基本模式(没有 -E
)并将每个 ?
替换为 \{0,1\}
(同样,0 或 1 个匹配项)或 *
(匹配 0 个或多个)。
这个问题比 sed
更适合 awk,因为 row/column 处理:
awk 'BEGIN{FS=OFS=","} NR==1 {
for (i=1; i<=NF; i++) {gsub(/^"|"$/, "", $i); $i = "\"" $i "\""}
} 1' file
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
- 使用
gsub
函数,我们删除前导或尾随双引号(如果存在) - 然后我们可以安全地将每个单元格用双引号括起来
通过先删除所有可能的双引号来保留现有的有效 sed 命令:
sed '1 { s/"//g; s/^/"/; s/,/","/g; s/$/"/ }' file.csv
为了测试每个答案,我创建了三个文件:
file.csv
word1,word2,word3,word4,word5
12345,12346,12347,12348,12349
file2.csv
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
file3.csv
"word1",word2,word3,"word4",word5
12345,12346,12347,12348,12349
然后我创建了一个bash脚本
#!/bin/bash
sed -E '1 { s/^"?/"/; s/"?,"?/","/g; s/"?$/"/ }' file.csv > final.csv
sed -E '1 { s/^"?/"/; s/"?,"?/","/g; s/"?$/"/ }' file2.csv > final2.csv
sed -E '1 { s/^"?/"/; s/"?,"?/","/g; s/"?$/"/ }' file3.csv > final3.csv
然后我查看了最终文件,第一行是完美的。
# cat final*.csv
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
"word1","word2","word3","word4","word5"
12345,12346,12347,12348,12349
带有 sed
和 awk
的正则表达式会受到一系列看似永无止境的失败边缘情况的影响。相反,利用 csv 库提供了更多的稳健性。
我发现 Python 的图书馆是最佳选择,因为它:
- 除Python本身外,广泛可用,没有繁重的依赖关系;
- 对您使用的 Python 版本不是特别敏感;
- 适合嵌入到 shell 脚本中;和
- 非常紧凑(单线就可以!)。
因此,我的解决方案是:
QUOTE_CSV_PY='import sys; import csv; csv.writer(sys.stdout, quoting=csv.QUOTE_ALL).writerows(csv.reader(sys.stdin))'
head -1 file.csv | python -c "$QUOTE_CSV_PY"; tail -n +2 file.csv
分解:
QUOTE_CSV_PY
是一个包含 Python 单行命令的 shell 变量- Python 命令只导入标准的
sys
和csv
模块。然后它创建一个 csv 编写器,写入stdout
并设置QUOTE_ALL
以便所有字段都被引用。它被馈送一个 csv reader 从stdin
. 读取
head -1
将第一行发送到 python 解释器进行处理。; tail -n +2
等到处理完成,然后转储从第二行开始的每一行。