在我的脚本中,如何区分何时使用星号通配符而不是强类型参数?
这个
# myscript *
由此
# myscript p1 p2 p3 ... (where parameters are unknown number)
我有一个类似的问题,但是我没有检测用户何时使用通配符调用脚本,而只是想防止使用通配符,并传递字符串预扩展。
如果您想检测,Tom的解决方案很好,但是我宁愿阻止。换句话说,如果我有一个名为findin的脚本,看起来像
#!/bin/bash
echo"[${1}]"
并使用以下命令运行它:
$ findin *
我希望输出会很简单
[*]
为此,您可以通过别名别名findin
alias findin='set -f; /path/to/findin'
但是随后您将为其余的会话设置shell选项。这可能会破坏许多不期望这样做的程序(例如ls -lh * .py)。您可以输入以下内容进行验证
echo $-
在控制台中。如果看到f,则设置该选项。
您可以通过键入以下内容手动清除该选项
set +f
在每次findin实例之后,但这将变得乏味而烦人。
由于shell脚本会生成子shell,并且您无法从脚本(set + f)中清除标志,因此我想到的解决方案如下:
g(){ /usr/local/bin/findin"$@"; set +f; }
alias findin='set -f; g'
注意:" g"可能不是该函数的最佳名称,因此建议您对其进行更改。
最后,您可以通过执行以下操作将其概括:
reset_expansion(){ CMD="$1"; shift; $CMD"$@"; set +f; }
alias findin='set -f; reset_expansion /usr/local/bin/findin'
这样,您希望禁用扩展的另一个脚本仅需要一个额外的别名,例如
alias newscript='set -f; reset_expansion /usr/local/bin/newscript'
而不是其他包装函数。
如果需要更长的篇幅,请参阅此处的我的文章。
外壳会扩展通配符。到脚本运行时,通配符已经展开,并且脚本无法分辨参数是通配符还是显式列表。
这意味着您的脚本将需要其他非脚本的帮助。具体来说,是在命令行处理之前运行的东西。那是别名。这是你的别名
alias myscript='set -f; globstopper /usr/bin/myscript'
这样做是建立一个名为" myscript"的别名,因此当有人键入" myscript"时,便会运行该别名。别名有两件事:首先,它使用set -f关闭通配符扩展,然后运行一个名为globstopper的函数,将脚本的路径以及其余的命令行参数传递进来。
那么什么是globstopper函数?这个:
globstopper() {
if [["$2" =="*" ]]
then echo"You cannot use a wildcard"
return
fi
set +f
"$@";
}
此功能执行三件事。首先,它检查脚本的参数是否为通配符(caveat:仅检查第一个参数,并且仅检查是否为简单的星形;将其扩展为涵盖更多情况作为练习)读者)。其次,它重新打开通配符扩展。最后,它运行原始命令。
为此,您确实需要能够在用户的shell中设置别名和shell函数,并要求您的用户使用别名而不是脚本。但是,如果您能够做到这一点,它应该可以工作。
我应该补充一点,我在这里主要依靠辉煌的西蒙·塔瑟姆(Simon Tatham)的文章"魔术别名:伯恩壳中的分层漏洞"。
西蒙·塔瑟姆(Simon Tathams)的论文很有趣,但它的前提是错误的:伯恩(Bourne)炮弹支持别名。真正的Bourne外壳不支持别名。只有高度扩展的Bourne外壳才可以使用,而无论它们仍然是Bourne外壳还是POSIX外壳或当时的其他东西,都是有争议的。
我谨此提出,该解决方案不是通用解决方案。在某些情况下,它似乎可以工作,但是usrbinmyscript仍然无法确定它最初是不是用通配符调用的。
@乔纳森:你说的完全正确,脚本说不清。但是脚本和别名的结合可以-我相信您总是可以使用这种方法来检测参数中的通配符;你能建议一个你做不到的情况吗?也就是说,如果您执行类似echo * |的操作。 xargs myscript,那么即使在原则上我也认为没有任何方法可以检测到。或echo *>一切;回声"时间过去"; xargs myscript
使用csh代替启用别名的Bourne / Korn / Bash Shell时?或者,当脚本由绝对路径名直接调用时。我不确定非交互式外壳是否一定要在设置别名的位置读取控制文件-部分取决于放置它的位置。 Perl脚本不会注意到它。我想知道make和类似程序。
你不能
它是Unix的优点之一(或从某些方面来说是缺点)。
请参见" UNIX-HATERS手册"中的" diatribe"。
您之所以不能这样做,是因为在脚本启动时,外壳程序已经扩展了通配符。
好吧,那我猜是那些将其视为弱点的人之一:)
哦,可以!遵循的答案...
附录
我在寻找命令行计算器的解决方法时找到了这篇文章:
alias c='set -f; call_clc'
where"call_clc" is the function:"function call_clc { clc"$*"; set +f; }"
and"clc" is the script
#!/bin/bash
echo"$*" | sed -e 's/ //g' >&1 | tee /dev/tty | bc
我需要'set -f'和'set +f'才能使诸如'c 4 * 3'之类的输入起作用,
因此,星号前后带有空白,
为了防止重击。
更新:以前的变体" alias c='set -f; clc"$*"; set+f;'"不起作用
因为出于某种原因,两次调用命令" c 4 * 4"后会给出正确的结果。
有人知道为什么会这样吗?
关于您以前的尝试为何无效的原因:别名无法接受参数,因此在使用别名时" $ *"可能扩展为零。函数似乎是首选解决方案-例如,请参见此处。我仍然不知道为什么在两次调用后它仍然可以工作。 Havent尝试过。
$arg_num == ***; // detects *(literally anything since it's a global wildcard)
$arg_num == *_*; // detects _
这是使用_的示例
for i in $*
do
if [["$i" == *_* ]];
then echo $i;
fi
done
./bash测试的输出* test2 _
_
./bash test * test2的输出,使用*********而不是****
test
bash
pass.rtf
test2
_
注意:*在bash中是如此全局,以至于它打印出与该描述匹配的文件,或者在我的情况下,在我还未使用的桌面上打印出与该描述匹配的文件。希望我能给您一个更好的答案,但最好的选择是使用*或其他脚本语言之外的其他语言。
只是注意是./bash测试 * test2 _被转义了*被读为" *"字符串而不是通配符
如果您觉得必须这样做,也许:
# if the number of parms is not the same as the number of files in cwd
# then user did not use *
dir_contents=(*)
if [["${#@}" -ne"${#dir_contents[@]}" ]]; then
used_star=false
else
# if one of the params is not a file in cwd
# then user did not use *
used_star=true
for f; do [[ ! -a"$f" ]] && { used_star=false; break; }; done
fi
unset dir_contents
$used_star && echo"used star" || echo"did not use star"
pedantically,如果用户实际使用星号,或者用户以任何顺序手动输入目录内容,这将回显"二手星号"。