win10 - windows7 bash




如何迭代Bash脚本中的参数 (4)

我有一个复杂的命令,我想创建一个shell / bash脚本。 我可以很容易地以$1$1写出来:

foo $1 args -o $1.ext

我希望能够将多个输入名称传递给脚本。 什么是正确的做法?

当然,我想处理其中有空格的文件名。


重写VonC现在删除的answer 。

Robert Gamble简洁的回答直接解决了这个问题。 这个放大了一些与包含空格的文件名有关的问题。

另请参阅: / bin / sh中的$ {1:+“$ @”}

基础论文: "[email protected]"是正确的, $* (无引号)几乎总是错的。 这是因为"[email protected]"在参数中包含空格时工作正常,并且在$*时不起作用。 在某些情况下, "$*"也可以,但"[email protected]"通常(但不总是)在相同的地方工作。 不加引号, [email protected]$*是等价的(并且几乎总是错误的)。

那么, $*[email protected]"$*""[email protected]"之间的区别是什么? 它们都与'壳的所有论点'有关,但它们做不同的事情。 当没有引用时, $*[email protected]做同样的事情。 他们将每个“单词”(非空白序列)视为一个单独的参数。 不过,引用的表单有很大不同: "$*"将参数列表视为单个空格分隔的字符串,而"[email protected]"将参数视为与在命令行上指定的参数几乎完全相同。 当没有位置参数时, "[email protected]"扩展到什么都没有; "$*"扩展为一个空字符串 - 是的,有一个区别,尽管它很难察觉它。 在引入(非标准)命令之后,请参阅下面的更多信息。

第二篇论文:如果你需要用空格处理参数然后将它们传递给其他命令,那么你有时需要非标准的工具来协助。 (或者你应该小心使用数组: "${array[@]}"行为类似于"[email protected]" 。)

例:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

为什么这不起作用? 它不起作用,因为shell在扩展变量之前会处理引号。 因此,为了让shell注意$var嵌入的引号,你必须使用eval

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

如果你有文件名,例如“ He said, "Don't do this!" “(带引号和双引号和空格),这会变得非常棘手。

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

这些shell(所有这些)并不会让它很容易处理这些东西,所以很多Unix程序都不能很好地处理它们。 在Unix上,文件名(单个组件)可以包含除斜杠和NUL '\0'以外的任何字符。 但是,shell强烈鼓励在路径名称中的任何位置不使用空格或换行符或制表符。 这也是为什么标准Unix文件名不包含空格等

在处理可能包含空格和其他麻烦字符的文件名时,您必须非常小心,而且很久以前我发现我需要一个在Unix上不是标准的程序。 我称它为escape (版本1.1的日期为1989-08-23T16:01:45Z)。

这是一个使用escape的例子 - 使用SCCS控制系统。 这是一个封面脚本,可以同时执行delta (认为签入 )和get (认为签出 )。 各种各样的论据,特别是-y (你为什么进行这个改变)会包含空白和换行符。 请注意,该脚本的日期从1992年开始,所以它使用back-ticks而不是$(cmd ...)表示法,并且不在第一行使用#!/bin/sh

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "[email protected]"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(我现在可能不会像现在这么彻底地使用转义 - 例如, -e参数不需要 - 但总体而言,这是使用escape更简单的脚本之一。)

escape程序只是简单地输出它的参数,就像echo一样,但是它确保参数被保护起来用于eval (一个eval级别;我有一个远程shell执行的程序,并且需要转义输出escape )。

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

我还有另一个名为al程序,每行列出一个参数(甚至更古老:版本1.1日期为1987-01-27T14:35:49)。 它在调试脚本时非常有用,因为它可以插入命令行以查看实际传递给命令的参数。

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[ 补充:现在为了显示各种"[email protected]"符号之间的区别,下面再举一个例子:

$ cat xx.sh
set -x
al [email protected]
al $*
al "$*"
al "[email protected]"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

请注意,在命令行上没有保留**/*之间的原始空白。 另外,请注意,您可以使用以下命令更改shell中的'命令行参数':

set -- -new -opt and "arg with space"

这设置了4个选项' -new ','- -opt ',' and ',' arg with spacearg with space '。
]

嗯,这是一个相当长的答案 - 也许注释是更好的术语。 请求时提供escape源代码(在Gmail点com上以电子​​邮件的形式发送给名字点姓氏)。 al的源代码非常简单:

#include <stdio.h>
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

就这样。 它等同于Robert Gamble展示的test.sh脚本,可以写成shell函数(但是当我第一次写al时,shell函数在本地版本的Bourne shell中不存在)。

另请注意,您可以将al编写为简单的shell脚本:

[ $# != 0 ] && printf "%s\n" "[email protected]"

条件是需要的,以便它在没有参数传递时不产生输出。 printf命令将只生成一个只有格式字符串参数的空行,但C程序不产生任何内容。


使用"[email protected]"来表示所有参数:

for var in "[email protected]"
do
    echo "$var"
done

这将迭代每个参数并将其打印在单独的行上。 $ @的行为与$ *相似,除了引用时,如果参数中有空格,则正确分解参数:

sh test.sh 1 2 '3 4'
1
2
3 4

您也可以将它们作为数组元素进行访问,例如,如果您不想遍历所有元素

argc=$#
argv=([email protected])

for (( j=0; j<argc; j++ )); do
    echo ${argv[j]}
done

请注意,罗伯特的答案是正确的,它也适用于sh 。 你可以(可移植地)更进一步简化它:

for i in "[email protected]"

相当于:

for i

也就是说,你不需要任何东西!

测试( $是命令提示符):

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "[email protected]"; do echo "$i"; done
a
b
spaces here
d

我首先在Kernighan和派克的Unix编程环境中读到这个。

bashhelp for文档:

for NAME [in WORDS ... ;] do COMMANDS; done

如果'in WORDS ...;' 不存在,则假设'in "[email protected]"'





command-line