为什么我在数字比较时收到命令未找到错误?
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
就风格而言,我更喜欢在与 for
或 while
声明相同的行上执行此操作,但没有关于此的约定。
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
的使用效率低下:因为您的模式是固定字符串而不是正则表达式,您可以使用 fgrep
或 grep -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"
我正在尝试解析文件的每一行并查找特定的字符串。该脚本似乎正在执行其预期的工作,但是,它同时尝试在第 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
就风格而言,我更喜欢在与 for
或 while
声明相同的行上执行此操作,但没有关于此的约定。
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
的使用效率低下:因为您的模式是固定字符串而不是正则表达式,您可以使用 fgrep
或 grep -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"