英文:
Command line testing of "if" works different if in interactive shell or script shell
问题
假设你有一个名为'test'的目录,在该目录中有一个'static'目录。
mkdir -p test/static
然后假设你有一个名为test.sh的脚本:
#!/bin/sh
set -xe
#arg is the directory to test if it's a ultima dir
is_static() {
ls "$1" 2> /dev/null | grep -qim1 '^static/$'
return $?
}
if is_static "$1"
then
echo 'is static'
fi
chmod +x test.sh
这个脚本的输出如下:
$ ./test.sh 'test'
+ is_static test
+ ls test
+ grep -qim1 '^static/$'
+ return 1
这已经让我感到惊讶,但我认为这是函数的一个错误,没什么大不了的。但后来我通过将函数复制粘贴到终端中并手动调用if语句来测试它:
将is_static函数复制粘贴到终端中,然后输入以下内容:
set -ex; if is_static 'test' ; then echo "success" ; fi ; set -ex
输出如下:
+ set -ex
+ is_static test
+ grep --color=auto -qim1 '^static/$'
+ ls -F test
+ return 0
+ echo success
success
+ set -ex
到底发生了什么?为什么交互式shell中的grep和ls调用颠倒了顺序,而在非交互式脚本中不起作用?如何使脚本工作?
我明白交互式shell会添加默认参数,这没什么大不了的… 除非我在脚本中的ls函数中添加'-F',突然之间脚本就“工作”了,但由于-F是:
-F, --classify
append indicator (one of */=>@|) to entries
我百分之百确定这是一个错误(但我仍然不确定为什么它会修复它)。
请注意,这是问题的翻译。如果你有任何进一步的问题或需要进一步的帮助,请随时提出。
英文:
Assume you have a dir named 'test' and inside that dir you have 'static'
mkdir -p test/static
Then assume you have this script as test.sh:
#!/bin/sh
set -xe
#arg is the directory to test if it's a ultima dir
is_static() {
ls "$1" 2> /dev/null | grep -qim1 '^static/$'
return $?
}
if is_static "$1"
then
echo 'is static'
fi
chmod +x test.sh
The output of this script here is:
$ ./test.sh 'test'
+ is_static test
+ ls test
+ grep -qim1 ^static/$
+ return 1
This is already surprising to me, but i was thinking it was a bug on the function, no big deal
but then i tested it by copy pasting the function in the file in the terminal and calling the if manually:
copy paste the is_static function into the terminal then write:
set -ex; if is_static 'test' ; then echo "success" ; fi ; set -ex
the output is:
+ set -ex
+ is_static test
+ grep --color=auto -qim1 '^static/$'
+ ls -F test
+ return 0
+ echo success
success
+ set -ex
what the hell is going on? Why is the grep and ls call inverted in the interactive shell, and why does it work there and not in the non-interactive script? And how can i make the script work?
I understand the interactive shell adds default arguments, that's not a big deal... except if i add '-F' to the ls in the function in the script suddenly the script 'works' but since -F is:
-F, --classify
append indicator (one of */=>@|) to entries
I'm 100% certain it's a red erring (but i'm still not certain why it fixes it).
答案1
得分: 1
正如oguz ismail提到的,问题在于使用grep测试需要使用-F选项,否则它不会将'/'添加到输出目录中。这在Ubuntu中是默认设置,但在非交互式shell中不是。
我最终这样做了:
find "$1" -mindepth 1 -maxdepth 1 -type d -iname "static" -print -quit | grep '.' > /dev/null; return "$?"
这个命令可能更加复杂,但似乎对于不寻常的路径或文件名来说可能会更"安全"。
解释一下,find可以进行不区分大小写的检查(iname),只检查"$1"的内容(使用-mindepth 1去掉'.'),只检查直接子目录(使用-maxdepth 1),并输出路径的其余部分(而不是"$1")。
由于使用-print和-quit,只会将一个'./static'(对于成功,不区分大小写)或空字符串(对于失败)放入管道中,grep '.'(任何字符)可以对其进行测试,而我没有在grep中使用-q,而是使用> /dev/null 2>&1
(我更喜欢&> /dev/null
,但它不符合POSIX标准),以防止将管道失败信号发送给find,因为某些shell环境不允许pipefail。
编辑:将最后一部分更改为> /dev/null
。抑制stderr听起来不是一个好主意。
英文:
As mentioned by oguz ismail the problem is that -F is necessary for the grep test to work because otherwise it doesn't add '/' to the output directories. This is the default in ubuntu, but not on non-interactive shells.
I ended up doing:
find "$1" -mindepth 1 -maxdepth 1 -type d -iname "static" -print -quit | grep '.' > /dev/null; return "$?"
Which is much more confusing but seems that it might be 'safer' for unusual paths or filenames.
To explain it, find can check without case sensitivity (iname) and only the "$1" contents (without '.' with -mindepth 1), only the immediate children with -maxdepth 1 and outputs the 'rest' of the path (not "$1").
Since with -print and -quit only one './static' (for success, in whatever case) or empty string (for failure) will be put into the pipe, grep '.' (any character) can test it, and i'm not using -q on the grep and instead > /dev/null 2>&1
(i'd prefer &> /dev/null
but it's not posix compliant) for a pipe failure signal not to be sent to find since some shell contexts do not tolerate pipefail.
edit: changed that last part to > /dev/null
. Suppressing stderr sounds like a bad idea.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论