我正在尝试解析文件的每一行并查找特定的字符串。该脚本似乎正在执行其预期的工作,但是,并行尝试在第6行执行if命令:
#!/bin/bash
for line in $(cat $1)
do
echo $line | grep -e "Oct/2015"
if($?==0); then
echo "current line is: $line"
fi
done
我得到以下内容(我的脚本是readlines.sh)
./readlines.sh: line 6: 0==0: command not found
答案 0 :(得分:2)
第一:正如Llama先生所说,你需要更多的空间。现在,您的脚本会尝试查找名为/usr/bin/0==0
的文件来运行。代替:
[ "$?" -eq 0 ] # POSIX-compliant numeric comparison
[ "$?" = 0 ] # POSIX-compliant string comparison
(( $? == 0 )) # bash-extended numeric comparison
第二:在这种情况下,根本不要测试$?
。事实上,你甚至没有充分理由使用grep
;以下两者都更有效(因为它只使用内置于bash中的功能,不需要调用外部命令)并且更具可读性:
if [[ $line = *"Oct/2015"* ]]; then
echo "Current line is: $line"
fi
如果您确实 需要使用grep
,请按以下方式编写:
if echo "$line" | grep -q "Oct/2015"; then
echo "Current line is: $line"
fi
这种方式if
直接在管道的退出状态下运行,而不是运行第二个命令测试$?
并在上运行命令&#39}退出状态。
答案 1 :(得分:2)
for line in $(cat $1)
正如我在其他地方的评论中所指出的,这应该是while read
构造而不是for cat
构造。
此外,当您{cat} $1
时,应引用该变量。如果没有引用空格,文件名中出现的其他不太常见的字符将导致cat失败,循环将不处理该文件。
完整的一行如下:
while IFS= read -r line
权衡can be found here的说明性示例。链接的测试脚本如下。我试图说明为什么IFS=
和-r
很重要。
#!/bin/bash
mkdir -p /tmp/testcase
pushd /tmp/testcase >/dev/null
printf '%s\n' '' two 'three three' '' ' five with leading spaces' 'c:\some\dos\path' '' > testfile
printf '\nwc -l testfile:\n'
wc -l testfile
printf '\n\nfor line in $(cat) ... \n\n'
let n=1
for line in $(cat testfile) ; do
echo line $n: "$line"
let n++
done
printf '\n\nfor line in "$(cat)" ... \n\n'
let n=1
for line in "$(cat testfile)" ; do
echo line $n: "$line"
let n++
done
let n=1
printf '\n\nwhile read ... \n\n'
while read line ; do
echo line $n: "$line"
let n++
done < testfile
printf '\n\nwhile IFS= read ... \n\n'
let n=1
while IFS= read line ; do
echo line $n: "$line"
let n++
done < testfile
printf '\n\nwhile IFS= read -r ... \n\n'
let n=1
while IFS= read -r line ; do
echo line $n: "$line"
let n++
done < testfile
rm -- testfile
popd >/dev/null
rmdir /tmp/testcase
请注意,这是一个重要的例子。例如,其他shell不会支持-r
进行读取,也不会let
移植。转到脚本的下一行。
do
作为一种风格问题,我更喜欢与for
或while
声明在同一行,但是没有约定。
echo $line | grep -e "Oct/2015"
此处应引用变量$line
。一般来说,除非你明确地知道更好的,否则你应该双重引用所有扩展 - 这意味着子单元和变量。这使您免受大多数意外的贝壳怪异的影响。
你宣布你的shell为bash
,这意味着你将拥有&#34;这里的字符串&#34;运营商<<<
可供您使用。可用时,它可用于避免管道;管道的每个元素都在子shell中执行,这会产生额外的开销,如果您尝试修改变量,可能会导致意外行为。这将写成
grep -e "Oct/2015" <<<"$line"
请注意,我引用了line
扩展。
您已使用grep
致电-e
,这不是不正确的,但由于您的模式不是以-
开头,因此不必要。另外,你在shell中有一个完整引用的字符串,但你不会尝试扩展变量或在其中使用其他shell插值。如果您不希望并且不希望shell引用字符串的内容被视为特殊字符串,则应单引号。此外,使用grep
是低效的:因为您的模式是固定字符串而不是正则表达式,您可以使用fgrep
或grep -F
,它使用字符串包含而不是正则表达式匹配(并因此而快得多)。所以这可能是
grep -F 'Oct/2015' <<<"$line"
不改变行为。
if($?==0); then
这是您原始问题的根源。在shell脚本中,命令由空格分隔;当你说if($?==0)
$?
扩展时,可能会为0,而bash
将尝试执行名为if(0==0)
的命令,这是一个合法的命令名称。你想要做的是调用if
命令并给它一些参数,这需要更多的空格。我相信其他人已经充分涵盖了这一点。
您永远不需要在shell脚本中测试$?
的值。 if
命令存在基于您传递给它的任何命令的返回代码的分支行为,因此您可以内联grep
调用并让if
直接检查其返回代码,因此:
if grep -F 'Oct/2015` <<<"$line" ; then
请注意;
分隔符周围的大量空格。我这样做是因为在shell中通常需要空格,并且只能省略某些内容。而不是试图记住你什么时候可以做,我建议在一切周围额外填充一个空间。它从来没有错,可以让其他错误更容易被注意到。
正如其他人已经注意到的那样grep
会将匹配的行打印到stdout,这可能不是你想要的。如果您使用的是Linux上的标准GNU grep
,那么您可以使用-q
开关。这将抑制grep
if grep -q -F 'Oct/2015' <<<"$line" ; then
如果您要严格遵守标准,或者在grep
并且不知道-q
的任何环境中,实现此效果的标准方法是将stdout重定向到{{ 1}}
/dev/null/
在这个例子中,我还删除了这里的字符串bashism,以显示该行的可移植版本。
if printf "$line" | grep -F 'Oct/2015' >/dev/null ; then
这一行的脚本没有任何问题,只是虽然echo "current line is: $line"
是标准的实现,但是它的程度不同,以至于它不可能完全依赖它的行为。您可以在echo
的任何地方使用printf
,并且您可以对其打印内容充满信心。即使echo
有一些警告:一些不常见的转义序列不是均匀支持的。有关详细信息,请参阅mascheck。
printf
注意最后的显式换行符; printf不会自动添加一个。
printf 'current line is: %s\n' "$line"
对这一行没有评论。
fi
如果您按照我的建议执行操作并将done
行替换为for
构造,则此行将更改为:
while read
这将done < "$1"
变量中文件的内容定向到$1
循环的标准输入,后者又将数据传递给while
。
为了清楚起见,我建议先将read
中的值复制到另一个变量中。这样,当你阅读这一行时,目的就更明确了。
我希望没有人会对上面提到的风格选择采取重大攻击,这是我试图注意到的;有很多方法可以做到这一点(但不是很多正确的)方式。
当您在将来遇到这样的困难时,请务必通过优秀的shellcheck和explain shell运行有趣的摘要。
最后,这里列出了所有内容:
$1
答案 2 :(得分:1)
如果你喜欢单行,你可以使用AND运算符(echo "$line" | grep -e "Oct/2015" && echo "current line is: $line"
),例如:
grep -qe "Oct/2015" <<<"$line" && echo "current line is: $line"
或:
{{1}}
答案 3 :(得分:0)
间距在shell脚本中非常重要 此外,双parens用于数字比较,而不是单个parens。
if (( $? == 0 )); then