dis*_*vox -2 shell shell-script
有人可以用一个例子告诉我每一行的含义是什么,我不明白为什么使用正则表达式,甚至 [!0122...]
#!/bin/sh
is_integer ()
{
case "${1#[+-]}" in
(*[!0123456789]*) return 1 ;;
('') return 1 ;;
(*) return 0 ;;
esac
}
Run Code Online (Sandbox Code Playgroud)
Sté*_*las 10
#!/bin/sh
Run Code Online (Sandbox Code Playgroud)
在 shell 的语法中是注释。但是,这会#!
告诉内核,在执行该文件时,/bin/sh
应该使用存储在该路径中的解释器来解释该文件,并且应该使用脚本的路径作为参数来执行。
is_integer () compound-command
Run Code Online (Sandbox Code Playgroud)
是用于定义函数的 POSIX sh 语法。
{
...
}
Run Code Online (Sandbox Code Playgroud)
是称为命令组的复合命令。它的唯一目的是对命令进行分组,在这里使其成为函数的主体。在这里,它是多余的,因为它的内容只是一个复合命令,但是使用命令组作为每个函数的主体是常见的做法,并且可以使代码更具可读性,因此通常建议使用。可以编写相同的函数:{ ... }
is_integer () case "${1#[+-]}" in
(*[!0123456789]*) return 1 ;;
('') return 1 ;;
(*) return 0 ;;
esac
Run Code Online (Sandbox Code Playgroud)
case something (pattern1 | pattern2) ...;; (pattern3)... ; esac
是一个case
/esac
构造(组成一个复合命令),它something
依次匹配每个模式,并在第一次匹配时执行相应的代码。
这something
是${1#[-+]}
。这是一个参数扩展,它将${param#pattern}
运算符应用于1
作为函数第一个参数的参数。该运算符从参数内容的开头剥离与模式匹配的最短字符串。[-+]
是匹配-
或+
字符的通配符模式(不是正则表达式)。所以${1#[-+]}
扩展为去掉符号的第一个参数的值。因此,如果第一个参数是-2
,则变为 2。如果是,-
则变为空字符串。如果2
是停留2
。
你会注意到"${1#[+-]}"
被引用了。通常,您需要引用参数扩展,否则它们会受到 split+glob 的影响。在这里,它是极少数不会发生的情况之一,所以严格来说,这些引用是多余的(但不要伤害并且仍然是好的做法)。
然后该值与某些模式匹配。
*[!0123456789]*
是 --*
任意数量的字符(尽管大多数 shell 也接受非字符)-- 后跟 --[!0123456789]
既不是也不0
是1
... 也不是9
-- 的任何字符(*
再次)。因此,它将匹配包含不是十进制数字的字符(或在大多数 shell 中为非字符)的任何字符串。
如果匹配,return 1
则执行代码,这将导致函数返回该1
退出代码,与 0 以外的任何数字一样,表示false / failure。
''
是表示空字符串的一种方式。空字符串也不是有效数字,但不会与前一个模式匹配。
然后*
匹配任何东西。因此,return 0
将针对不匹配任何先前模式的任何字符串运行。这里是多余的,因为该case
语句是该函数中的最后一个命令,如果没有在其中运行命令,则case
语句返回成功/真。
所以在这里,该函数定义可以缩短为:
is_integer() case ${1#[-+]} in
('' | *[!0123456789]*) false
esac
Run Code Online (Sandbox Code Playgroud)
虽然这并没有使它更清晰。
在任何情况下,该代码都适合使用[0123456789]
. 特别是对于输入验证(和它的当它在外壳算术表达式的二手验证输入的关键,看到壳牌算术评估使用unsanitized数据的安全影响),[0-9]
或者[[:digit:]]
应该不被使用,特别是如果你sh
的实现是bash
因为[0-9]
可以匹配任何字符(或可能的多字符排序元素)在 0 和 9 之间排序,并且[[:digit:]]
在某些 BSD 上将匹配任何十进制数字系统的数字,不仅是 0123456789 英语系统,甚至在英语语言环境中也是如此。
例如,GNU系统上,在一个典型的美国英语语言环境(这些天倾向于使用UTF-8字符集的),在bash
,[0-9]
还会匹配上,
,
和数以百计的其他字符)。而在 FreeBSD 上,在相同的语言环境中,
[[:digit:]]
将匹配数百个不同的字符(包括)。
例如,如果您在输入验证期间通过,则不会关闭那些任意代码注入漏洞的路径。在
ksh
GNU 系统中和在 GNU 系统上,是一个有效的变量名(对于由 匹配的许多其他字符也是如此
[0-9]
)。如果该变量已设置(例如在环境中)并包含a[0$(reboot>&2)]
,则:
is_integer "$1" || exit
echo "$(( $1 + 1 ))"
Run Code Online (Sandbox Code Playgroud)
如果is_integer
无法拒绝该输入,则在 ksh 中将导致重新启动。
要使用正则表达式进行匹配,您需要expr
or awk
,尽管很少有 shell 内置这些命令,因此效率会降低。一些[
实现,比如[
内置的zsh
oryash
也可以进行正则表达式匹配。并且一些 shell 还具有[[ ... ]]
可以进行正则表达式匹配的条件表达式构造,但这些都不是标准的,sh
并且在输入验证方面有自己的问题。
虽然*
大多数sh
实现中的shell 通配符将匹配字节序列,即使其中一些不形成有效字符,同样 for [!0123456789]
,.*
或[^0123456789]
regexp 等价物通常不会。
在这里,只要匹配是肯定的,这可能不是问题。做一个负匹配,如:
regexp() {
awk -- 'BEGIN {exit !(ARGV[1] ~ ARGV[2])}' "$@"
}
is_integer() {
! regexp "${1#[-+]}" '^(.*[^012345679].*)?$'
}
Run Code Online (Sandbox Code Playgroud)
由于该case
语句的直接翻译是错误的,因为它无法拒绝包含未形成有效字符的字节序列的输入,但是
is_number() {
regexp "$1" '^[-+]?[0123456789]+$'
}
Run Code Online (Sandbox Code Playgroud)
应该没问题,因为它会拒绝任何包含未形成有效字符的字节序列的输入。