Bash:拆分字符串就像Readline将它拆分一样

时间:2012-01-20 09:59:04

标签: string bash split readline

给定任意Bash“simple command”的字符串表示,如何将其拆分为包含其各个“部分”的数组,即命令名和各个参数,就像shell一样本身(即Readline)会在解析它时决定它并决定运行哪个可执行文件/函数以及传递哪些参数?


我的特定用例需要解析用户定义的别名定义。例如。别名可能定义为:

alias c2="cut -d' ' -f2"  # just an example... arbitrary commands should be handled!

这就是我的bash脚本尝试解析它的方式:

alias_name="c2"
alias_definition=$(alias -p | grep "^alias $alias_name=") # "alias c2='cut -d'\'' '\'' -f2'"
alias_command=${alias_definition##alias $alias_name=}     # "'cut -d'\'' '\'' -f2'"
alias_command=$(eval "echo $alias_command")               # "cut -d' ' -f2"

alias_parts=($alias_command) # WRONG - SPLITS AT EVERY WHITESPACE!

echo "command name: ${alias_parts[0]}"

for (( i=1; i <= ${#alias_parts}; i++ )); do
  echo "parameter $i : ${alias_parts[$i]}"
done

输出:

command name: cut
parameter 1 : -d'
parameter 2 : '
parameter 3 : -f2

期望的输出:

command name: cut
argument 1  : -d' '
argument 2  : -f2

为了达到这个目的,我需要用alias_parts=($alias_command)替换为什么?

5 个答案:

答案 0 :(得分:2)

如l0b0所说,它不是readline。这是shell本身正在分裂。所以使用shell本身来进行解析。

alias c2="cut -d' ' -f2"

split_parts() {
    alias_parts=("$@")
}

alias_defn=$(alias c2)
# 2 evals needed to get rid of quotes
eval eval split_parts ${alias_defn#alias c2=}

for (( i=0; i < ${#alias_parts}; i++ )); do
  echo "parameter $i : \"${alias_parts[$i]}\""
done

输出

parameter 0 : "cut"
parameter 1 : "-d "
parameter 2 : "-f2"

请注意,-d包含shell实际看到的尾随空格。

答案 1 :(得分:1)

最小化“邪恶的otto”解决方案:

alias c2="cut -d' ' -f2"
alias_definition=$(alias c2)
eval eval alias_parts=( "${alias_definition#alias c2=}" )

您可以使用`declare -p'进行快速数组打印:

$ declare -p alias_parts
declare -a alias_parts='([0]="cut" [1]="-d " [2]="-f2")'

同样有用的可能是`printf%q'引用一个参数“以一种可以作为shell输入重用的方式”(来自:help printf):

$ printf %q ${alias_parts[1]}
-d\
弗雷迪·沃尔托(Freddy Vulto) http://fvue.nl/wiki/Bash

答案 2 :(得分:0)

不是readline分裂,而是getoptgetoptsFor example

params="$(getopt -o d:h -l directory:,help --name "$0" -- "$@")"

eval set -- "$params"
unset params

while true
do
    case "${1-}" in
        -d|--directory)
            directory="$2"
            shift 2
            ;;
        -h|--help)
            usage
            exit
            ;;
        --)
            shift
            if [ "${1+defined}" = defined ]
            then
                usage
            fi
            break
            ;;
        *)
            usage
            ;;
    esac
done

答案 3 :(得分:0)

set内置可用于分割字符串。

bash$ set -- cut -d ' ' -f2

bash$ echo "'$3'"
' '

编辑:如果要拆分的字符串已经在变量中,那就太麻烦了。您可以使用eval,但在这种情况下,我会说这会使事情变得复杂,而不是简化它们。

bash$ a="cut -d ' ' -f2"

bash$ eval set -- $a  # No quoting!

bash$ echo "'$3'"
' '

答案 4 :(得分:0)

如果我们将每个alias_command的参数放在它自己的行上,然后(本地) 设置IFS=\n,我们已经完成了:

parsealias ()
{
   alias_command_spaces=$(eval "echo $(alias $1)" | sed -e "s/alias $1=//") # "cut -d' ' -f2"
   alias_command_nl=$(eval each_arg_on_new_line $alias_command_spaces)      # "cut\n-d' '\n-f2"
   local IFS=$'\n' # split on newlines, not on spaces
   alias_parts=($alias_command_nl) # each line becomes an array element, just what we need
   # now do useful things with alias_parts ....
}

现在我们只需要编写上面使用的命令each_arg_on_new_line,例如:

#!/usr/bin/env perl

foreach (@ARGV) {
  s/(\s+)/'$1'/g; # put spaces whithin quotes
  print "$_\n";
}