如何在shell脚本参数中处理“ - ”?

时间:2011-05-25 11:00:10

标签: bash shell zsh sh

这个问题有三个部分,每个部分都很简单,但结合在一起并不是微不足道的(至少对我而言):))

需要编写一个应该作为其参数的脚本:

  1. 另一个命令的一个名称
  2. 命令的几个参数
  3. 文件列表
  4. 示例:

    ./my_script head -100 a.txt b.txt ./xxx/*.txt
    ./my_script sed -n 's/xxx/aaa/' *.txt
    

    等等。

    出于某种原因我的脚本内部需要区分

    • 命令是什么
    • 命令
    • 的参数是什么
    • 文件是什么

    所以编写上述示例的最标准方法可能是:

    ./my_script head -100 -- a.txt b.txt ./xxx/*.txt
    ./my_script sed -n 's/xxx/aaa/' -- *.txt
    

    问题1:这里有更好的解决方案吗?

    ./my_script中的处理(第一次尝试):

    command="$1";shift
    args=`echo $* | sed 's/--.*//'`
    filenames=`echo $* | sed 's/.*--//'`
    
    #... some additional processing ...
    
    "$command" "$args" $filenames #execute the command with args and files
    

    filenames包含spaces和/或' - '时,此解决方案将失败,例如
    / some - path / to / more / idiotic file name.txt

    问题2:如何正确获取$command $args$filenames以供日后执行?

    问题3: - 如何实现以下执行方式?

    echo $filenames | $command $args #but want one filename = one line (like ls -1)
    

    这里是不错的shell解决方案,还是需要使用例如perl?

3 个答案:

答案 0 :(得分:6)

首先,听起来你正在尝试编写一个带有命令和文件名列表的脚本,并依次对每个文件名运行命令。这可以在bash的一行中完成:

$ for file in a.txt b.txt ./xxx/*.txt;do head -100 "$file";done
$ for file in *.txt; do sed -n 's/xxx/aaa/' "$file";done

然而,也许我误解了你的意图所以让我单独回答你的问题。

而不是使用“ - ”(已经有不同的含义),以下语法对我来说更自然:

./my_script -c "head -100" a.txt b.txt ./xxx/*.txt
./my_script -c "sed -n 's/xxx/aaa/'" *.txt

要在bash中提取参数,请使用getopts

SCRIPT=$0

while getopts "c:" opt; do
    case $opt in
        c)
            command=$OPTARG
            ;;
    esac
done

shift $((OPTIND-1))

if [ -z "$command" ] || [ -z "$*" ]; then
    echo "Usage: $SCRIPT -c <command> file [file..]"
    exit
fi

如果要为每个剩余的参数运行命令,它将如下所示:

for target in "$@";do
     eval $command \"$target\"
done

如果你想从STDIN读取文件名,它看起来会更像这样:

while read target; do
     eval $command \"$target\"
done

答案 1 :(得分:3)

引用$@变量时,可以将参数分组:

for parameter in "$@"
do
    echo "The parameter is '$parameter'"
done

如果给出:

head -100 test this "File name" out

将打印

the parameter is 'head'
the parameter is '-100'
the parameter is 'test'
the parameter is 'this'
the parameter is 'File name'
the parameter is 'out'

现在,您所要做的就是解析循环。您可以使用一些非常简单的规则:

  1. 第一个参数始终是文件名
  2. 以破折号开头的后续参数是参数
  3. 在“ - ”之后或者一次不以“ - ”开头,其余的都是文件名。
  4. 您可以使用以下方法检查参数中的第一个字符是否为破折号:

    if [[ "x${parameter}" == "x${parameter#-}" ]]
    

    如果您之前没有看过这种语法,那就是左侧过滤器#将变量名的两部分分开。第一部分是变量的名称,第二部分是要切断的 glob 过滤器(不是正则表达式)。在这种情况下,它是一个短划线。只要这个陈述不正确,你知道你有一个参数。顺便说一下,在这种情况下可能需要或不需要x。当你运行一个测试,并且你有一个带有破折号的字符串时,测试可能会将它误认为测试参数而不是值。

    把它放在一起会是这样的:

    parameterFlag=""
    for parameter in "$@"     #Quotes are important!
    do
        if [[ "x${parameter}" == "x${parameter#-}" ]]
        then
             parameterFlag="Tripped!"
        fi
        if [[ "x${parameter}" == "x--" ]]
        then
             print "Parameter \"$parameter\" ends the parameter list"
             parameterFlag="TRIPPED!"
        fi
        if [ -n $parameterFlag ]
        then
            print "\"$parameter\" is a file"
        else
            echo "The parameter \"$parameter\" is a parameter"
        fi
    done
    

答案 2 :(得分:0)

问题1

我不这么认为,至少不是你需要为任意命令做这件事。

问题3

command=$1
shift
while [ $1 != '--' ]; do
    args="$args $1"
    shift
done
shift
while [ -n "$1" ]; do
    echo "$1"
    shift
done | $command $args

问题2

与问题3有何不同?