当处理输入文件时,复制/更新和重命名是一种有效的方法?

当处理输入文件时,复制/更新和重命名是一种有效的方法?

我有一个脚本,将 2 个文件作为输入。
在处理开始之前,需要对文件进行一些准备。
我的想法是不碰原始文件,而是尽一切努力进行副本,打印所需的内容作为输出并删除副本。
然而,这种方法使脚本具有许多变量,并且很容易出错。
例子:

#!/bin/bash                                                                                                      
[[ -z $1 ]] && echo 'We need input file a' && exit 1;  
[[ -z $2 ]] && echo 'We need input file b' && exit 1;  

A_CSV=$1;  
B_CSV=$2;  

A_FILE="$A_CSV.tmp";  
B_FILE="$B_CSV.tmp";  

[ -f $A_FILE ]] && rm $A_FILE;  
[[ -f $B_FILE ]] && rm $B_FILE;  

tr -d "\r" < $A_CSV >  $A_FILE;  
tr -d "\r" < $B_CSV > $B_FILE;  

awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' $A_FILE > "$A_FILE.bck";
awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' $B_FILE > "$B_FILE.bck";

rm $A_FILE && mv "$A_FILE.bck" $A_FILE;   
rm $B_FILE && mv "$B_FILE.bck" $B_FILE;   
# extra logic following the same pattern  

您可以看到如何创建副本来一遍又一遍地进行更新和重命名。

有没有办法改进这一点以使脚本不易出错?

答案1

这是通过管道 ( |) 实现的。有很多很好的教程,例如 这个

#!/bin/bash
[[ -z $1 ]] && echo 'We need input file a' && exit 1;
[[ -z $2 ]] && echo 'We need input file b' && exit 1;  

A_CSV=$1;  
B_CSV=$2;  

A_FILE="$A_CSV.tmp";  
B_FILE="$B_CSV.tmp";  

[ -f $A_FILE ]] && rm $A_FILE;
[[ -f $B_FILE ]] && rm $B_FILE;

tr -d "\r" < $A_CSV | awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' > $A_FILE
tr -d "\r" < $B_CSV | awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' > $B_FILE

我个人会创建一个函数来处理单个操作,因为您对两个文件执行相同的操作。 rm -f $A_FILE $B_FILE我认为也会看起来更好。

答案2

保留原始文件完整并在副本上进行处理是一个非常好的主意。您应该更进一步,也不要重复使用中间文件。如果您重复使用中间文件,并且该过程被中断,您将无法知道它在哪一点被中断。

您正在对两个文件应用相同的转换。不要将代码写两次!编写一次代码,根据需要使用变量,并为每个文件调用一次该代码。在 shell 脚本中,执行此操作的工具是编写功能(或者,如果您需要从多个脚本调用该代码段,请将其设为一个单独的脚本)。

您使用的所有文本处理工具都可以从标准输入读取并写入标准输出。您可以通过放置一个来组合它们管道一个工具的输出和下一个工具的输入之间。这样,您就不需要那么多中间文件 - 事实上,在这种情况下您不需要任何中间文件。管道是 Unix 的一个基本设计特征。

进一步的 shell 编程技巧:始终在变量扩展两边加上双引号, IE $foo

#!/bin/bash                                                                                                      

preprocess_csv () {
  <"$1" \
  tr -d '\r' |
  awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' >"${1%.csv}.clean"
}

preprocess_csv "$1"
preprocess_csv "$2"

do_stuff_with_preprocessed_file "${1%.csv}.clean" "${2%.csv}.clean" >global_output

我用的是参数扩展构造${1%.csv}将eg 转换foo.csvfoo,以便此转换的输出文件为foo.clean

该脚本比您的脚本更简单,但仍然可以改进。有比 shell 脚本更好的工具来描述文件处理命令链:构建自动化工具比如经典的制作。看执行带有检查点的命令列表?有关类似用例的介绍。以下是如何使用 make 来表达您所拥有的转换。调用此文件Makefile。请注意,下面的行缩进了 8 个空格,您需要用制表符替换这 8 个空格,这是 make 的一个怪癖。

default: global_output

%.clean: %.csv
        <'$<' tr -d '\r' | awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' >'$@'

global_output: input1.clean input2.clean
        do_stuff_with_preprocessed_files input1.clean input2.clean >$@

$<in a command 代表依赖(上面右边的文件target: dependency),$@代表目标。使用上面的 makefile,如果您运行命令make global_output(或者只是运行命令make,感谢default:开头的行),它将运行转换以生成文件.clean.csv文件必须已经存在),然后它将运行do_stuff_with_preprocessed_files以生成global_output.

该 makefile 很脆弱,因为如果中途中断,它会留下部分处理的文件。要解决此问题,请在每个规则中使用临时文件,如中所述执行带有检查点的命令列表?

default: global_output

%.clean: %.csv
        <'$<' tr -d '\r' | awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' >'[email protected]'
        mv '[email protected]' '$@'

global_output: input1.clean input2.clean
        do_stuff_with_preprocessed_files input1.clean input2.clean >'[email protected]'
        mv '[email protected]' '$@'

相关内容