我正在编写一个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'
我很欣赏任何有关失败原因的见解。我觉得我很亲密。谢谢!
答案 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.csv
和current-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