命令行测试 “if” 在交互式 shell 和脚本 shell 中的工作方式不同。

huangapple go评论61阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2023年5月29日 15:01:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76355263.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定