我遇到了一个 bash 脚本问题,它以意想不到的顺序回显输出。脚本如下。问题出在第 30-32 行的输出上。
1 IFS=$'\n'
2 i=1
3 bluered=""
4 blueyellow=""
5 redyellow=""
6 all=""
7 while [ $i -le `cat sorted.csv | wc -l` ]
8 do
9 for j in {0..2}
10 do
11 # cat sorted.csv | head -$i | tail -1 | awk -F',' '{print $1}'
12 declare "`cat sorted.csv | head -$i | tail -1 | awk -F',' '{print $1}'`=`cat sorted.csv | head -$i | tail -1 | awk -F',' '{print $5}'`"
13 i=$((i+1))
14 done
15
16 if [[ ${blue} == ${red} ]]; then bluered=1; else bluered=0; fi
17 if [[ ${blue} == ${yellow} ]]; then blueyellow=1; else blueyellow=0; fi
18 if [[ ${red} == ${yellow} ]]; then redyellow=1; else redyellow=0; fi
19 if [[ ${blue} == ${red} ]] && [[ ${red} == ${yellow} ]]; then all=1; else all=0; fi
20
21 echo "`cat sorted.csv | head -$((i-3)) | tail -1`"
22 echo ",$all,$bluered,$blueyellow,$redyellow"
23 echo "`cat sorted.csv | head -$((i-2)) | tail -1`"
24 echo ",$all,$bluered,$blueyellow,$redyellow"
25 echo "`cat sorted.csv | head -$((i-1)) | tail -1`"
26 echo ",""$all"",""$bluered"",""$blueyellow"",""$redyellow"
27
28
29
30 echo "`cat sorted.csv | head -$((i-3)) | tail -1`,$all,$bluered,$blueyellow,$redyellow"
31 echo "`cat sorted.csv | head -$((i-2)) | tail -1`"",$all,$bluered,$blueyellow,$redyellow"
32 echo "`cat sorted.csv | head -$((i-1)) | tail -1`"",""$all"",""$bluered"",""$blueyellow"",""$redyellow"
33 done
第 30-32 行的双引号格式略有不同,因为我尝试了不同的方法使其正常工作。第 21-26 行只不过是将第 30-32 行分解为两部分(即第 21-22 行与第 30 行相同)。
根据输入文件“sorted.csv”,第 30-32 行(输入文件的前 3 行)的正确输出应该是:
blue,1,WCC131035882,0,e89d89d7ca7c502ca8d3b2e0d7c4980dba346a63d57a437d8f1428065fb83e9f,0,0,0,1
red,1,Z292V5DB,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68,0,0,0,2
yellow,1,Z292V94K,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68,0,0,0,1
但实际的输出是:
,0,0,0,1CC131035882,0,e89d89d7ca7c502ca8d3b2e0d7c4980dba346a63d57a437d8f1428065fb83e9f #(line 30 output)
,0,0,0,192V5DB,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68 #(line 31 output)
,0,0,0,1,Z292V94K,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68 #(line 32 output)
第 21 - 26 行返回以下输出:
blue,1,WCC131035882,0,e89d89d7ca7c502ca8d3b2e0d7c4980dba346a63d57a437d8f1428065fb83e9f #(line 21 output)
,0,0,0,1 #(line 22 output)
red,1,Z292V5DB,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68 #(line 23output)
,0,0,0,1 #(line 24 output)
yellow,1,Z292V94K,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68 #(line 25)
,0,0,0,1 #(line 26 output)
简而言之,我想使用 3 个单行命令(如第 30-32 行)连接第 21-22、23-24 和 25-26 行的输出(但语法正确)。请注意,脚本中包含第 21-26 行只是为了证明第 30 行(31 或 32)的两个部分在分成两行时工作正常。目前,第 30 行有效地连接了第 22 行和第 21 行的输出,而不是第 21 行和第 22 行的输出。但是,在执行此反向连接时,它还会截断第 21 行输出的前 8 个字符(请注意,第 22 行的输出正好是 8 个字符)。
我该如何正确书写第 30 至 32 行以便它们创建所需的输出?
提前感谢你的帮助。
答案1
比简洁的“使用dos2unix
”更具洞察力。
显然sorted.csv
使用 CR+LF 行尾,而它应该使用仅限低频。
`something`
当您在 的输出末尾使用换行符 (LF) 时something
, 将被删除,但回车符 (CR) 不会被删除。 在您的例子中,text+CR+LF
变成了text+CR
。 如果它是 的唯一输入echo
,该工具会像往常一样添加换行符,并且您再次在末尾看到 CR+LF。 在将 打印到控制台时,此 CR 不会发生任何变化。
但是如果echo "`foo`bar"
返回的 CR 字符foo
停留在结果字符串的中间,那么后面的任何内容都会从控制台的左边缘打印出来,覆盖前面的部分。
解决方案是使用dos2unix sorted.csv
,因为你已经注意到。
但还有更多:
- 无用地使用
cat
$(stuff)
和有什么区别`stuff`
?- 为什么
printf
比 更好echo
? - 很多出现的
""
(例如第 32 行)什么都不做。它们是关闭+打开,而不是打开+关闭。它们只会混淆代码。我知道它们是“实验性的”,我明确地说它们什么都没改变。
和或许
echo $(stuff)
或有什么问题echo `stuff`
?我承认这个反对意见在这里是值得怀疑的。
"`foo`bar"
从 的输出中去除任何尾随的 LFfoo
并将其与 连接起来很有用bar
;在你的情况下这很重要。然后echo "`foo`"
使用类似的语法来演示行为上的相关差异,你明确地说明了这一点。
dos2unix
当然没有回答这个问题,即[强调我的]:
我如何正确地写入第 30 至 32 行以便它们创建所需的输出?
正确编写的第 32 行至少应使用printf
,而不应使用cat
。反引号和过多的引号可能会保留,但使用合理的引号会使代码更具可读性$( … )
。您也可以考虑将格式与数据分开,使用 很容易printf
:
printf '%s,%s,%s,%s,%s\n' "$( <sorted.csv head -$((i-1)) | tail -1 )" "$all" "$bluered" "$blueyellow" "$redyellow"
此外其他地方也存在一些糟糕的解决方案:
while [ $i -le `cat sorted.csv | wc -l` ]
(除了cat
和反引号)。没有必要在每次迭代时获取行数。wc -l sorted.csv
应该在循环之前运行一次,其结果存储在变量中(除非您预计行数在执行过程中会发生变化,但我认为您不会;如果数字不变,脚本的逻辑会更有意义)。你一遍又一遍地重新阅读文件。行数越多,你重新打开的次数就越多,从头开始阅读,然后挑选一个单身的每次一行。应该重新设计流程,以便逐行解析文件,可能不需要先前的
wc -l
,可能采用管道式方式(即只打开一次,只读取一次而不跳回)。由于您使用declare
内置函数,while IFS= read -r … ; done <sorted.csv
可能是必须的(可能带有初步的awk
,例如<sorted.csv awk … | while IFS= read -r …
)。您可以read
三次对三个不同的变量执行操作;然后读取接下来的三行。read
本身效率低下,但仍然比多次重新打开文件更优雅。注意,文件中每增加一行,脚本的效率就会降低;如果文件大到足以read
体现出效率低下的问题,您的方法可能会表现得更糟。但如果你坚持这么做的话,这整个改变将不会是微不足道的。
文件名不应该在这么多地方硬编码。只需读取一次文件自然就会减少这个问题。即使这样,最好从头开始
input_file="sorted.csv"
并在需要时使用"$input_file"
。如果您决定将文件路径作为命令行参数传递,那么只需输入input_file="$1"
.为什么没有事情发生?
从总体考虑,更正确的第 30-32 行应如下代码片段所示:
#!/bin/bash
input_file="sorted.csv
…
while IFS= read -r pre_previous_line && IFS= read -r previous_line && IFS= read -r current_line; do
…
# useful bashism instead of echo "$previous_line" | some_command
some_command <<< "$previous_line"
…
# printf will reuse the format if there are more arguments than the format needs
# so this one line will be enough for your three
# (split for readability, it's still one line for the shell)
printf '%s,%s,%s,%s,%s\n' \
"$pre_previous_line" "$all" "$bluered" "$blueyellow" "$redyellow" \
"$previous_line" "$all" "$bluered" "$blueyellow" "$redyellow" \
"$current_line" "$all" "$bluered" "$blueyellow" "$redyellow"
…
done <"$input_file"
也许仅awk
作为过滤器就可以做到这一点;文件将通过管道传输到它。awk
您也可以将信息存储在变量中,以便以后使用。条件也是可用的。像这样测试不充分例子:
#!/usr/bin/awk -f
BEGIN { FS="," }
{
if ($1 == "blue") blue=$5
if ($1 == "red") red=$5
if ($1 == "yellow") yellow=$5
if (NR%3 == 1) prepre=$0
if (NR%3 == 2) pre=$0
if (NR%3 == 0)
{
bluered=($blue == $red)
blueyellow=($blue == $yellow)
redyellow=($red == $yellow)
all= bluered * blueyellow
suffix=","all","bluered","blueyellow","redyellow
print prepre suffix
print pre suffix
print $0 suffix
}
}
(注意:awk
我测试的是mawk
)。保存它,使其可执行,并通过管道传输sorted.csv
它(例如<sorted.csv ./the_script
)。
答案2
关于文件中有回车符的评论完全正确。使用 dox2unix 运行输入文件后,脚本按预期运行。谢谢,Gordon。