验证 Bash 脚本的参数

Validating parameters to a Bash script

提问人:nickf 提问时间:3/31/2009 最后编辑:codeforesternickf 更新时间:8/14/2020 访问量:209689

问:

我想出了一个基本的方法来帮助自动删除一些不需要的文件夹的过程。

#!/bin/bash
rm -rf ~/myfolder1/$1/anotherfolder
rm -rf ~/myfolder2/$1/yetanotherfolder
rm -rf ~/myfolder3/$1/thisisafolder

这是这样唤起的:

./myscript.sh <{id-number}>

问题是,如果你忘记输入(就像我刚才所做的那样),那么它可能会删除很多你真的不想删除的东西。id-number

有没有办法可以向命令行参数添加任何形式的验证?就我而言,最好检查 a) 有一个参数,b) 它是数字,以及 c) 该文件夹存在;在继续脚本之前。

验证 bash shell

评论


答:

8赞 guns 3/31/2009 #1

使用 '-z' 测试空字符串,使用 '-d 检查目录。

if [[ -z "$@" ]]; then
    echo >&2 "You must supply an argument!"
    exit 1
elif [[ ! -d "$@" ]]; then
    echo >&2 "$@ is not a valid directory!"
    exit 1
fi

评论

2赞 vehomzzz 9/29/2012
为什么需要双 [[ ]]?
178赞 Brian Campbell 3/31/2009 #2
#!/bin/sh
die () {
    echo >&2 "$@"
    exit 1
}

[ "$#" -eq 1 ] || die "1 argument required, $# provided"
echo $1 | grep -E -q '^[0-9]+$' || die "Numeric argument required, $1 provided"

while read dir 
do
    [ -d "$dir" ] || die "Directory $dir does not exist"
    rm -rf "$dir"
done <<EOF
~/myfolder1/$1/anotherfolder 
~/myfolder2/$1/yetanotherfolder 
~/myfolder3/$1/thisisafolder
EOF

编辑:我错过了关于首先检查目录是否存在的部分,所以我添加了它,完成了脚本。此外,还解决了评论中提出的问题;修复了正则表达式,从 切换到 .==eq

据我所知,这应该是一个可移植的、符合 POSIX 标准的脚本;它不使用任何 bashisms,这实际上很重要,因为在 Ubuntu 上实际上是现在,而不是./bin/shdashbash

评论

0赞 guns 3/31/2009
请记住设置 +e 并使用 '-eq' 而不是 '==' 进行整数比较
0赞 Brian Campbell 3/31/2009
将其更改为 -eq;SET +e 在这里能买到你什么?
0赞 Johannes Schaub - litb 3/31/2009
我在我的答案中发现了两件事,您可能还想在您的答案中修复:首先,SO hilighter 为 $# 疯狂(将其视为评论)。我做了“$#”来修复它。其次,正则表达式也匹配“foo123bar”。我通过做^[0-9]+$来修复它。您也可以使用 grep 的 -x 选项来修复它
1赞 Brian Campbell 3/31/2009
@ojblass我错过了他询问的一项测试。添加它意味着还要添加他的目录进行测试,这大大扩展了答案的大小,因为它们不能放在一行上。您能否建议一种更紧凑的方法来测试每个目录的存在?
1赞 martin jakubik 6/1/2011
根据下面@Morten尼尔森的回答评论,grep '$[0-9]+^' 看起来确实很奇怪。不应该是'^[0-9]+$'吗?
24赞 Boiler Bill 3/31/2009 #3

不像上面的答案那样防弹,但仍然有效:

#!/bin/bash
if [ "$1" = "" ]
then
  echo "Usage: $0 <id number to be cleaned up>"
  exit
fi

# rm commands go here

评论

3赞 Shmygol 5/21/2022
它应该是确保命令返回失败的代码。exit 1
14赞 whaley 3/31/2009 #4

test () 的手册页提供了可在 bash 中用作布尔运算符的所有可用运算符。在脚本(或函数)的开头使用这些标志进行输入验证,就像在任何其他编程语言中一样。例如:man test

if [ -z $1 ] ; then
  echo "First parameter needed!" && exit 1;
fi

if [ -z $2 ] ; then
  echo "Second parameter needed!" && exit 2;
fi
16赞 Johannes Schaub - litb 3/31/2009 #5

我会使用 bash 的:[[

if [[ ! ("$#" == 1 && $1 =~ ^[0-9]+$ && -d $1) ]]; then 
    echo 'Please pass a number that corresponds to a directory'
    exit 1
fi

我发现这个常见问题解答是一个很好的信息来源。

22赞 lhunath 3/31/2009 #6

解决方案虽然高尚且执行良好,但也存在一些问题,所以我想我会提供自己的解决方案。shBrian Campbellbash

一个问题:sh

  • 中的波浪号不会扩展到您在 heredocs 中的主目录。当它被语句阅读或在语句中引用时,两者都不是。这意味着您会遇到错误。~/fooreadrmNo such file or directory
  • 对于基本操作来说,分叉等是愚蠢的。尤其是当你使用一个蹩脚的外壳来避免 bash 的“沉重”重量时。grep
  • 我还注意到一些引用问题,例如他的 .echo
  • 虽然很少见,但该解决方案无法处理包含换行符的文件名。(几乎没有任何解决方案可以应对它们 - 这就是为什么我几乎总是更喜欢,如果使用得当,它更防弹,更难利用)。shbash

虽然,是的,用于你的哈希爆炸意味着你必须不惜一切代价避免主义,但你可以使用你喜欢的所有主义,即使在 Ubuntu 或其他什么地方,当你诚实并放在顶部时。/bin/shbashbash#!/bin/bash

所以,这里有一个更小、更干净、更透明、可能“更快”、更防弹的解决方案。bash

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. 请注意声明中的引号!$1rm
  2. 如果为空,检查也会失败,因此这是两个检查合二为一。-d$1
  3. 我避免使用正则表达式是有原因的。如果你必须在 bash 中使用,你应该把正则表达式放在一个变量中。无论如何,像我这样的 glob 总是更可取的,并且在更多的 bash 版本中得到了支持。=~

评论

1赞 grinch 10/31/2014
那么,通配件 bash 是特定的吗?$1 != *[^0-9]*
5赞 MattK 3/26/2010 #7

您可以通过执行如下操作来紧凑地验证点 a 和 b:

#!/bin/sh
MYVAL=$(echo ${1} | awk '/^[0-9]+$/')
MYVAL=${MYVAL:?"Usage - testparms <number>"}
echo ${MYVAL}

这给了我们......

$ ./testparams.sh 
Usage - testparms <number>

$ ./testparams.sh 1234
1234

$ ./testparams.sh abcd
Usage - testparms <number>

此方法在 sh 中应该可以正常工作。

13赞 shmichael 12/7/2010 #8

使用 它将导致任何未设置的参数引用立即使脚本失败。set -u

请参阅文章:编写健壮的 Bash Shell 脚本 - David Pashley.com

评论

0赞 Manfred Moser 10/4/2013
在许多情况下,这是一个非常简单的修复程序,也适用于函数。谢谢!
0赞 Scott Hutchinson 8/31/2022
我确信这是一个好主意,但我可以从那篇文章中了解如何做到这一点。
1赞 Xarses 12/22/2010 #9

旧帖子,但我想我无论如何都可以做出贡献。

可以说,脚本不是必需的,并且可以从命令行执行对通配符具有一定的容忍度。

  1. 狂野无处不在的匹配。让我们删除任何出现的子“文件夹”

    $ rm -rf ~/*/folder/*
    
  2. Shell 迭代。让我们用一行删除特定的 pre 和 post 文件夹

    $ rm -rf ~/foo{1,2,3}/folder/{ab,cd,ef}
    
  3. Shell 迭代 + var(BASH 测试)。

    $ var=bar rm -rf ~/foo{1,2,3}/${var}/{ab,cd,ef}
    
9赞 AndrewD 1/27/2020 #10

一行 Bash 参数验证,带和不带目录验证

以下是一些对我有用的方法。可以在全局脚本命名空间中使用它们(如果在全局命名空间中,则不能引用函数内置变量)

快速而肮脏的一个衬垫

: ${1?' You forgot to supply a directory name'}

输出:

./my_script: line 279: 1: You forgot to supply a directory name

Fancier - 供应函数名称和用法

${1? ERROR Function: ${FUNCNAME[0]}() Usage: " ${FUNCNAME[0]} directory_name"}

输出:

./my_script: line 288: 1:  ERROR Function: deleteFolders() Usage:  deleteFolders directory_name

添加复杂的验证逻辑,而不会使当前函数混乱

在接收参数的函数或脚本中添加以下行。

: ${1?'forgot to supply a directory name'} && validate $1 || die 'Please supply a valid directory'

然后,您可以创建一个验证函数,该函数执行类似操作

validate() {

    #validate input and  & return 1 if failed, 0 if succeed
    if [[ ! -d "$1" ]]; then
        return 1
    fi
}

以及一个在失败时中止脚本的 die 函数

die() { echo "$*" 1>&2 ; exit 1; }

对于其他参数,只需添加一行,复制格式即可。

: ${1?' You forgot to supply the first argument'}
: ${2?' You forgot to supply the second argument'}