以更好的方式分配变量值

时间:2016-10-13 00:03:48

标签: linux bash shell

我写了一个使用多个参数的bash脚本。 我已按以下格式分配变量。

x=$1
y=$2
z=$3
k=$4

参数是可选的,它也可以在没有它们的情况下运行 例如:

./myscript.sh
./myscript.sh x y ...

两种情况都正常。

我正在寻找一种更好的方法,从设计的角度来看,因为我不喜欢变量获得价值的方式。

如果将来我的论据增加到9

,那将不会很好看

感谢。

6 个答案:

答案 0 :(得分:2)

您可以read结合使用herestring和printf的引用重建功能:

read x y z k <<<$(printf " %q" "$@")

以示例:

$ cat example.bash
#!/bin/bash
read x y z k <<<$(printf " %q" "$@")
echo "x=[$x]"
echo "y=[$y]"
echo "z=[$z]"
echo "k=[$k]"

$ ./example.bash a b "c d"
x=[a]
y=[b]
z=[c d]
k=[]

那么,这里发生了什么?让我们从内到外工作。

printf " %q" "$@" quotes以与原始命令行参数等效的方式给出的参数。如果没有这种引用重构,带空格的命令行参数将被视为单独的参数,即使最初引用。要查看我的意思,请尝试read x y z w <<<"$@":z指定为“c”,k指定为“d”。

read接收重构的命令行,然后从左到右将每个非转义空格分隔的字符串分配给给定的变量。

回到我们的例子:

  • "$@"基本上是a b "c d"
  • printf " %q" "$@"a b c\ d
  • read x y z k <<<"a b c\ d"是您想要的硬编码表示。

虽然这是紧凑和可扩展的,但它也很棘手。如果您的脚本使用代表选项的参数(脚本行为根据缺少所述参数而发生变化),那么我建议使用getopts。但是,如果您的脚本采用了许多表示的参数(比如输入到矩阵计算中),那么读入数组(read -a)可能更容易理解。

您可能还希望处理不提供命令行参数的情况。这需要略微阐述:

read x y z k <<<$([ 0 -eq $# ] && echo '' || printf " %q" "$@")

在此变体中,将检查参数的数量,如果存在一些参数,则执行printf requoting业务。

答案 1 :(得分:1)

你可以使用这个结构。

for i in x y z k 
do
    eval $i='$1'
    shift
done

答案 2 :(得分:1)

如果参数数量开放式,您最好使用数组 - 甚至可能"$@" ,所有参数的数组,直接(将所有参数复制到自定义数组,使用类似args=( "$@" )的内容)。

您可以了解有关Bash数组here的更多信息 此外,下面的解决方案通过使用辅助数组来创建单个变量来演示阵列技术。

那就是说,如果你需要不同的变量名 ,这是一个强有力的方法:

为了参数化和错误处理,这种方法可能比它需要的更间接 有关此方法的要点,请参阅rené's helpful answer

#!/usr/bin/env bash

# Declare the up to 9 variable names to assign to, using a helper array.
varNames=( a b c d e f g h i )

# Exit, if more arguments than available variables were specified.
(( $# > ${#varNames[@]} )) && { echo "Too many arguments." >&2; exit 2; }

# Assign to the variables in sequence, looping over the variable-names array.
for varName in "${varNames[@]}"; do
  (( $# )) || break   # Break, if there are no more arguments.
  declare "$varName"="$1"
  shift
done

然后使用variable indirection打印生成的变量:

# Print all variable values, using indirection.
for varName in "${varNames[@]}"; do  
  echo "Value of \$$varName: '${!varName}'"
done

答案 3 :(得分:1)

通过在命令行上命名参数来完全跳过位置参数:

$ cat example.bash
#!/bin/bash
declare x=1013 y=242 z k
declare "$@" >/dev/null
echo "x=[$x]"
echo "y=[$y]"
echo "z=[$z]"
echo "k=[$k]"

$ ./example.bash x="b" z="c d" k=$'e\nf'
x=[b]
y=[242]
z=[c d]
k=[e
f]

第一个declare充当安全网,并使用默认值初始化所有预期变量。第二个declare在命令行中提取命名变量,将它们视为变量赋值。命令行中未提供的变量保留其初始化的默认值(如示例中所示,y未传递,因此默认值为242)。声明之外的变量将在您的脚本中提供。这可能是也可能不是。

答案 4 :(得分:0)

使用“shift”可以将命令行参数作为命名选项。 见Handling positional parameters

while :
do
    case "$1" in
      -f | --file)
          file="$2"   
          shift 2
          ;;
      -h | --help)
          display_help  # Call your function
          # no shifting needed here, we're done.
          exit 0
          ;;
      -u | --user)
          username="$2" # You may want to check validity of $2
          shift 2
          ;;
      -v | --verbose)
          #  It's better to assign a string, than a number like "verbose=1"
          #  because if you're debugging the script with "bash -x" code like this:
          #
          #    if [ "$verbose" ] ...
          #
          #  You will see:
          #
          #    if [ "verbose" ] ...
          #
          #  Instead of cryptic
          #
          #    if [ "1" ] ...
          #
          verbose="verbose"
          shift
          ;;
      --) # End of all options
          shift
          break;
          ;;
      -*) # Wrong option
          echo "Error: Unknown option: $1" >&2
          exit 1
          ;;
      *)  # No more options
          break
          ;;
    esac
done

或者您可以使用getopts

答案 5 :(得分:0)

在任何情况下都不可能为变量分配零字节$'\0' c中的字符串以零字节结尾,不能包含零字节 下面不再讨论这个问题。

没有空格,制表符或换行符的变量

如果您不需要允许空格或换行符,则可以有效地运行:

if (IFS=''; reg=$'[ \t\n]'; [[ "$*" =~ $reg ]] ); then
    echo "$0: error: input contains spaces, tabs or newlines" >&2
    exit 1
fi

if (( $# != 4 )); then
    echo "$0: error: we need 4 arguments"
    exit 2
fi

read x y z k <<<"$@"

printf 'x="%s" y="%s" z="%s" k="%s"\n' "$x" "$y" "$z" "$k"

如果输入有空格,制表符或换行符,则脚本将禁止。

将其用作:

$ script one two t33 f44
x="one"   y="two"   z="t33"   k="f44"

没有换行符的变量

要使其接受空格和制表符,我们需要将读取扩展为:

IFS=$'\n' read -d '' x y z k < <(printf '%s\n' "$@")

$ ./script $'o\tne' 't wo' t33 f44
x="o    ne"   y="t wo"   z="t33"   k="f44"

一个合理的选择是使用关联数组(无新行):

if (IFS=''; reg=$'[\n]'; [[ "$*" =~ $reg ]] ); then 
    echo "$0: error: input contains newlines" >&2
    exit 1
fi

varlist=(x y z k)
n=${#varlist[@]}

if (( $# != $n )); then
echo "$0: error: we need $n arguments"
exit 2
fi

declare -A arr
for i in ${varlist[@]}; do
IFS='' read "arr[$i]" 
done < <(printf '%s\n' "$@")

for i in ${!arr[@]}; do
printf '%s="%s"    ' "$i" "${arr[$i]}"
done
echo

某些更改允许直接使用变量名称:

if (IFS=''; reg=$'[\n]'; [[ "$*" =~ $reg ]] ); then 
    echo "$0: error: input contains newlines" >&2
    exit 1
fi

varlist=(x y z k)
n=${#varlist[@]}

if (( $# != $n )); then
    echo "$0: error: we need $n arguments"
    exit 2
fi

for i in ${varlist[@]}; do
    IFS='' read "$i"
done < <(printf '%s\n' "$@")

for i in ${varlist[@]}; do
    printf '%s="%s"    ' "$i" "${!i}"
done
echo

最后的改编允许最终接受换行:

varlist=(x y z k)
n=${#varlist[@]}

if (( $# != $n )); then
    echo "$0: error: we need $n arguments"
    exit 2
fi

for i in ${varlist[@]}; do
    IFS='' read -d '' "$i"
done < <(printf '%s\0' "$@")

for i in ${varlist[@]}; do
    printf '%s="%s"    ' "$i" "${!i}"
done
echo