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

发布于 2024-11-09 18:31:20 字数 1218 浏览 5 评论 0原文

这个问题有 3 个部分,每个部分都很简单,但组合在一起并不简单(至少对我来说):)

需要编写一个脚本,应将其参数作为其参数:

  1. 另一个命令的一个名称
  2. 文件命令
  3. 列表的

几个参数举例:

./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

文件名包含空格和/或“--”时,此解决方案将失败,例如
/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?

This question has 3 parts, and each alone is easy, but combined together is not trivial (at least for me) :)

Need write a script what should take as its arguments:

  1. one name of another command
  2. several arguments for the command
  3. list of files

Examples:

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

and so on.

Inside my script for some reason I need distinguish

  • what is the command
  • what are the arguments for the command
  • what are the files

so probably the most standard way write the above examples is:

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

Question1: Is here any better solution?

Processing in ./my_script (first attempt):

command="$1";shift
args=`echo $* | sed 's/--.*//'`
filenames=`echo $* | sed 's/.*--//'`

#... some additional processing ...

"$command" "$args" $filenames #execute the command with args and files

This solution will fail when the filenames will contain spaces and/or '--', e.g.
/some--path/to/more/idiotic file name.txt

Question2: How properly get $command its $args and $filenames for the later execution?

Question3: - how to achieve the following style of execution?

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

Is here nice shell solution, or need to use for example perl?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

岁月苍老的讽刺 2024-11-16 18:31:20

首先,听起来您正在尝试编写一个脚本,该脚本接受命令和文件名列表,并依次对每个文件名运行该命令。这可以在 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

First of all, it sounds like you're trying to write a script that takes a command and a list of filenames and runs the command on each filename in turn. This can be done in one line in 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

However, maybe I've misinterpreted your intent so let me answer your questions individually.

Instead of using "--" (which already has a different meaning), the following syntax feels more natural to me:

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

To extract the arguments in bash, use 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

If you want to run a command for each of the remaining arguments, it would look like this:

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

If you want to read the filenames from STDIN, it would look more like this:

while read target; do
     eval $command \"$target\"
done
早乙女 2024-11-16 18:31:20

$@ 变量在引用时将能够按应有的方式对参数进行分组:

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. 在“--”之后或一旦不以“-”开头,其余的都是参数文件名。

您可以使用以下命令检查参数中的第一个字符是否为破折号:

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

The $@ variable, when quoted will be able to group parameters as they should be:

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

If given:

head -100 test this "File name" out

Will print

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'

Now, all you have to do is parse the loop out. You can use some very simple rules:

  1. The first parameter is always the file name
  2. The parameters that follow that start with a dash are parameters
  3. After the "--" or once one doesn't start with a "-", the rest are all file names.

You can check to see if the first character in the parameter is a dash by using this:

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

If you haven't seen this syntax before, it's a left filter. The # divides the two parts of the variable name. The first part is the name of the variable, and the second is the glob filter (not regular expression) to cut off. In this case, it's a single dash. As long as this statement isn't true, you know you have a parameter. BTW, the x may or may not be needed in this case. When you run a test, and you have a string with a dash in it, the test might mistake it for a parameter of the test and not the value.

Put it together would be something like this:

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
新雨望断虹 2024-11-16 18:31:20

问题 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 有什么不同?

Question 1

I don't think so, at least not if you need to do this for arbitrary commands.

Question 3

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

Question 2

How does that differ from question 3?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文