为什么我在数字比较时收到命令未找到错误?

Why am I getting command not found error on numeric comparison?

我正在尝试解析文件的每一行并查找特定的字符串。该脚本似乎正在执行其预期的工作,但是,它同时尝试在第 6 行执行 if 命令:

#!/bin/bash
for line in $(cat )
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

间距在 shell 脚本中很重要。
此外,双括号用于数值比较,而不是单括号。

if (( $? == 0 )); then

首先:正如骆驼先生所说,您需要更多空间。现在您的脚本试图寻找一个名为 /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 直接对管道的退出状态进行操作,而不是 运行 测试第二个命令 $? 并在 that[=31= 上操作] 命令的退出状态。

如果你喜欢一行,你可以使用AND运算符(&&),例如:

echo "$line" | grep -e "Oct/2015" && echo "current line is: $line"

或:

grep -qe "Oct/2015" <<<"$line" && echo "current line is: $line"

@Charles Duffy 有一个很好的答案,我认为它是正确的(确实如此),但这里有一个详细的、逐行的脚本分解以及每个部分的正确操作。

for line in $(cat )

正如我在其他地方的评论中指出的那样,这应该作为 while read 构造而不是 for cat 构造来完成。

  • 此构造将分词每一行,使文件中的 space 与输出中的 "lines" 分开。
  • 将跳过所有空行。

另外当你 cat </code> 变量应该被引用。如果不加引号 spaces 等文件名中出现的不常用字符会导致 cat 失败,循环不会处理该文件。</p> <p>完整的行应该是:</p> <pre><code>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

请注意,这是一个 bash 的例子。例如,其他 shell 不倾向于支持 -r 读取,let 也不是可移植的。转到脚本的下一行。

do

就风格而言,我更喜欢在与 forwhile 声明相同的行上执行此操作,但没有关于此的约定。

echo $line | grep -e "Oct/2015"

此处应引用变量$line。一般来说,意思是 always 除非你特别了解 ,你应该双引号所有扩展——这意味着 subshells 以及变量。这使您免受最意想不到的 shell 怪异。

您将 shell 声明为 bash,这意味着您可以使用 "Here string" 运算符 <<<。如果可用,它可以用来避开管道;管道的每个元素都在子 shell 中执行,这会产生额外的开销,并且如果您尝试修改变量可能会导致意外行为。这将写成

grep -e "Oct/2015" <<<"$line"

请注意,我引用了 line 扩展。

您用 -e 调用了 grep,这没有错,但没有必要,因为您的模式不是以 - 开头的。此外,您在 shell 中用全引号引用了一个字符串,但您没有尝试扩展变量或在其中使用其他 shell 插值。当您不希望也不希望 shell 将带引号的字符串的内容视为特殊内容时,您应该将它们用单引号引起来。此外,您对 grep 的使用效率低下:因为您的模式是固定字符串而不是正则表达式,您可以使用 fgrepgrep -F,它包含字符串而不是正则表达式匹配(因此速度要快得多)。所以这可能是

grep -F 'Oct/2015' <<<"$line"

不改变行为。

if($?==0); then

这是您原来问题的根源。在shell 脚本中,命令由白色space 分隔;当你说 if($?==0) 时,$? 会扩展,可能会扩展到 0,而 bash 将尝试执行名为 if(0==0) 的命令,这是一个合法的命令名称。你想要做的是调用 if 命令并给它一些参数,这需要更多的 whitespace。我相信其他人已经充分介绍了这一点。

您永远不需要在 shell 脚本中测试 $? 的值。 if 命令存在基于你传递给它的任何命令的 return 代码的分支行为,所以你可以内联你的 grep 调用并让 if 检查它的 return直接编码,因此:

if grep -F 'Oct/2015` <<<"$line" ; then

请注意 ; 分隔符周围的宽大白色 space。我这样做是因为在 shell 中通常需要 whitespace 并且有时只能省略。与其试图记住你什么时候可以做什么,我建议在所有内容周围多加一个 space 填充。它永远不会出错,并且可以使其他错误更容易被注意到。

正如其他人所指出的那样,grep 会将匹配的行打印到标准输出,这可能不是您想要的。如果您正在使用 Linux 上的标准 GNU grep,您将可以使用 -q 开关。这将抑制 grep

的输出
if grep -q -F 'Oct/2015' <<<"$line" ; then

如果您试图严格遵守标准或在任何环境中 grep 不知道 -q 实现此效果的标准方法是将 stdout 重定向到 /dev/null/

if printf "$line" | grep -F 'Oct/2015' >/dev/null ; then

在这个例子中,我还删除了这里的字符串 bashism 只是为了显示该行的可移植版本。

echo "current line is: $line"

您的脚本的这一行没有任何问题,只是尽管 echo 是标准的实现,但其变化程度如此之大以至于不可能完全依赖其行为。您可以在任何需要使用 echo 的地方使用 printf 并且您可以相当确信它会打印什么。即使 printf 也有一些注意事项:一些不常见的转义序列没有得到均匀支持。有关详细信息,请参阅 mascheck

printf 'current line is: %s\n' "$line"

注意末尾的换行符; printf 不会自动添加一个。

    fi

此行暂无评论。

done

如果您按照我的建议将 for 行替换为 while read 构造,则此行将更改为:

done < ""

这会将 </code> 变量中的文件内容定向到 <code>while 循环的标准输入,后者又将数据传递给 read.

为了清楚起见,我建议先将 </code> 中的值复制到另一个变量中。这样当你读到这一行时,目的就更清楚了。</p> <p>我希望没有人对上面做出的文体选择产生太大的冒犯,我已经试图注意到这一点;有很多方法可以做到这一点(但不是很多<em>正确</em>)的方法。 </p> <p>当你将来 运行 遇到这样的困难时,一定要始终 运行 通过优秀 <a href="http://www.shellcheck.net/" rel="nofollow">shellcheck</a> and <a href="http://explainshell.com/" rel="nofollow">explain shell</a> 有趣的片段。</p> <p>最后,所有内容放在一起:</p> <pre><code>#!/bin/bash input_file="" while IFS= read -r line ; do if grep -q -F 'Oct/2015' <<<"$line" ; then printf 'current line is %s\n' "$line" fi done < "$input_file"