使用子shell将参数替换为diff

时间:2011-11-23 01:59:55

标签: bash shell

我正在编写一个shell脚本,为了使它更简单易读,我试图使用嵌套的子shell将参数传递给diff。

这就是我所拥有的:

if
  diff -iy '$(sort '$(awk 'BEGIN { FS = "|" } ; {print $1}' new-participants-by-state.csv)' '$(awk 'BEGIN { FS = "|" } ; {print $1}' current-participants-by-state.csv)')' > /dev/null;
then  
  echo There is no difference between the files. > ./participants-by-state-results.txt;
else  
  diff -iy '$(sort '$(awk 'BEGIN { FS = "|" } ; {print $1}' new-participants-by-state.csv)' '$(awk 'BEGIN { FS = "|" } ; {print $1}' current-participants-by-state.csv)')' > ./participants-by-state-results.txt;
fi

当我运行脚本时,我不断获得diff: extra operand 'AL'

我很欣赏任何有关失败原因的见解。我觉得我很亲密。谢谢!

2 个答案:

答案 0 :(得分:5)

您的代码无法读取,因为行太长了:

if diff -iy '$(sort '$(awk 'BEGIN { FS = "|" } ; {print $1}' new-participants-by-state.csv)' \
       '$(awk 'BEGIN { FS = "|" } ; {print $1}' current-participants-by-state.csv)')' \
       > /dev/null;
then  
    echo There is no difference between the files. > ./participants-by-state-results.txt;
else  
   diff -iy '$(sort '$(awk 'BEGIN { FS = "|" } ; {print $1}' new-participants-by-state.csv)' \
      '$(awk 'BEGIN { FS = "|" } ; {print $1}' current-participants-by-state.csv)')' \
      > ./participants-by-state-results.txt;
fi

重复这样的整个命令也相当讨厌。您使用单引号也存在重大问题;你在每组命令中只有一种,显然是在两个相同的awk命令的组合输出上运行(而你可能需要两个单独的排序,一个用于每个awk命令的输出);如果可以的话,你没有使用-F选项awk;你正在重复这个地方庞大的文件名;最后,看起来你可能想要使用进程替换,但实际上并没有这样做。

让我们退一步,明确提出问题。

  • 给定两个文件(new-participants-by-state.csvcurrent-participants-by-state.csv)在每个文件的每一行上找到第一个以管道分隔的字段,对这些字段的列表进行排序,并比较两个排序列表的结果。
  • 如果没有差异,请在输出文件participants-by-state-results.txt中写入消息;否则,列出输出文件中的差异。

所以,我们可以使用:

oldfile='current-participants-by-state.csv'
newfile='new-participants-by-state.csv'
outfile='participants-by-state-results.txt'

tmpfile=${TMPDIR:-/tmp}/xx.$$

awk -F'|' '{print $1}' $oldfile | sort > $tmpfile.1
awk -F'|' '{print $1}' $newfile | sort > $tmpfile.2

if diff -iy $tmpfile.1 $tmpfile.2 > $outfile
then echo "There is no difference between the files" > $outfile
fi

rm -f $tmpfile.?

如果这将是最终的脚本,我们希望将陷阱处理到位,这样就不会留下临时文件,除非脚本被SIGKILL杀死。

但是,我们现在可以使用进程替换来避免临时文件:

oldfile='current-participants-by-state.csv'
newfile='new-participants-by-state.csv'
outfile='participants-by-state-results.txt'

if diff -iy <(awk -F'|' '{print $1}' $oldfile | sort) \
            <(awk -F'|' '{print $1}' $newfile | sort) > $outfile
then echo "There is no difference between the files" > $outfile
fi

请注意代码如何在存在对称性的情况下仔细保留对称性。请注意使用短变量名称以避免重复长文件名。请注意,diff命令只运行一次,而不是两次 - 丢弃以后需要的结果不是很明智。

您可以使用以下方法压缩输出I / O重定向:

{
if diff -iy <(awk -F'|' '{print $1}' $oldfile | sort) \
            <(awk -F'|' '{print $1}' $newfile | sort)
then echo "There is no difference between the files"
fi
} > $outfile

将附带命令的标准输出发送到文件。

当然,如果文件是以管道分隔而不是逗号分隔的,CSV可能不是合适的术语,但这完全是另一回事。

我还假设来自diff -iy的状态按照原始脚本的建议工作;我没有验证diff命令的用法。

答案 1 :(得分:3)

这里有几个问题。

首先,您将各种参数放在单引号中,这会阻止对它们进行任何解释(例如,$(....)在单引号内不做任何特殊操作)。你可能会想到双引号,但这些并不是你想要的。

这给我们带来了第二个问题,即diff和sort期望被赋予文件名作为参数,并且它们对这些文件中的数据进行操作;你试图直接将数据作为参数传递,这不起作用(我怀疑这是你得到的错误的起源:diff只需要两个文件名,你传递的是两个以上的参与者名字,而AL恰好是名单上的第三名,因此是一个惊慌失措的人。通常的方法是使用中间文件(以及脚本中的多行),但bash实际上有一种方法可以执行此操作,而无需其中任何一个:process substitution。本质上,它所做的是运行一个带有输出(或输入,但我们需要在这种情况下输出)的命令发送到命名管道;然后它将管道的名称作为参数传递给另一个命令。例如,diff <(command1) <(command2)将为您提供command1和command2的输出之间的差异。请注意,由于这是仅限bash的功能,因此您必须使用#!/bin/bash启动脚本,而不是#!/bin/sh

第三,有一个缺少的近括号,这使得有点难以分辨应该发生的事情。是否应该在比较之前对两个文件进行排序,还是仅对新参与者文件进行排序?

第四,由于最终比较忽略大小写(-i),因此最好使用不区分大小写的排序(-f)。

最后,如果存在任何差异,您将完成所有处理两次。我建议将比较一次运行到文件中,然后如果没有差异则忽略/覆盖(空)文件。

哦,只是一个风格的东西:在bash的行尾你不需要分号。如果您在同一行上放置多个命令(以及then语句中的if之前的其他一些情况),则只需要分号。

无论如何,这是我的重写:

#!/bin/bash
if
    diff -iy <(awk 'BEGIN { FS = "|" } ; {print $1}' new-participants-by-state.csv | sort -f) <(awk 'BEGIN { FS = "|" } ; {print $1}' current-participants-by-state.csv | sort -f) >./participants-by-state-results.txt
then
    echo "There is no difference between the files." > ./participants-by-state-results.txt
fi