我编写了以下脚本,尝试实现自动化,对我处理的文本文件集进行一些常见的转换:
#!/bin/bash
set -e
if [ $# -eq 0 ]; then
echo "USAGE: $0 FILE"
echo "USAGE: Pass a colon-delimited text file as input."
exit
else
for i; do
echo "Checking file \"$i\" is colon-delimited..." &&
head "$i" | while IFS= read -r line; do
if [[ " $line " =~ ':' ]]; then continue;
else
echo "Input \"$i\" is not colon-delimited. Leaving file."
continue 2
fi
done
echo "Transforming file \"$i\"..." &&
awk -F: '$1!="" && $2!=""' "$i" | # Filter out lines with empty columns
awk -F: '{gsub(/ /, "", $1); print $1 FS $2}' | # Remove all spaces in first column; assumes two columns delimited by ':'
grep -aE ":|@" | # Filter out lines that don't contain '@' or ':'
tr -d '\000-\011\013-\037' | # Remove all control characters apart from (Linux) newline
awk 'length >=7 && length <=150' | # Filter out very short and long lines
LC_ALL=C sort -u > "${i%_final.*}" #&& # Sort and deduplicate
done
fi
预期行为
将文件作为输入,如果未给出输入则退出。读取每个输入文件的前 10 行以检查它是否包含冒号。如果包含,则将转换应用于文件;如果其中一行不包含冒号,则跳过该文件并开始处理下一个文件。
实际行为
大部分脚本均可正常工作;如果没有输入,脚本就会退出,并成功确定文件是否以冒号分隔。
问题仅限于for
循环:无论我尝试多少种变化break
,continue
它们最终都会以相同的结果结束 - 所有文件都会被转换,无论它们是否以冒号分隔。
运行脚本bash -n
并壳牌检测没有显示任何问题。
所有这些都指向一个非常简单的逻辑问题,但我已经研究了好几天,仍然无法将我的逻辑表达成代码。
这是我运行命令时得到的输出:
$ time ../transform_files.sh *
Checking file "file1" is colon-delimited...
Transforming file "file1"...
awk: cmd. line:1: (FILENAME=- FNR=4) warning: Invalid multibyte data detected. There may be a mismatch between your data and your locale.
Checking file "file2.sh" is colon-delimited...
Input "file2.sh" is not colon-delimited. Leaving file.
Transforming file "file2.sh"...
Checking file "file3.in.txt" is colon-delimited...
Transforming file "file3.in.txt"...
awk: cmd. line:1: (FILENAME=- FNR=44) warning: Invalid multibyte data detected. There may be a mismatch between your data and your locale.
Checking file "file4.txt" is colon-delimited...
Input "file4.txt" is not colon-delimited. Leaving file.
Transforming file "file4.txt"...
real 0m4.375s
user 0m0.349s
sys 0m0.805s
注意:我知道awk
可以使用 修复错误LC_ALL=C
,但我的输入文件往往包含大量无法丢弃的非 ASCII 字符。
答案1
我认为逻辑本身还不错。问题是continue
只能影响执行它的 shell 的流程。
Bash 在子 shell 上下文中运行管道的右侧。代码的相关片段是管道;continue 2
位于子 shell 中:
head "$i" | while …
…
continue 2
…
done
for
这就是为什么它不能影响你的循环流程外部子壳。
人们通常在以下情况下发现 Bash 的这种行为read
“不起作用”。对你来说,这是continue 2
“不起作用”的,但原因是一样的。解决方案也是如此:
while …
不在管道中运行:while … done < <(head "$i")
或者告诉 Bash 改变其行为。告诉它在当前 shell 环境中运行管道的最后一个命令:
# before the troublesome pipeline shopt -s lastpipe