Unix Bourne Shell中的数组


26

我正在尝试在Bourne shell(/bin/sh)中使用数组。我发现初始化数组元素的方法是:

arr=(1 2 3)

但是它遇到了一个错误:

syntax error at line 8: `arr=' unexpected

现在我发现该语法的帖子说的是for bash,但是我找不到Bourne shell的任何单独语法。语法/bin/sh也一样吗?


1
检查这个问题stackoverflow.com/questions/9481702/…在堆栈溢出时
Nischay 2014年

1
Thnx @Nischay ...阅读了您提供的链接后,我在google中优化了查询字符串并获得了链接-docstore.mik.ua/orelly/unix/upt/ch45_34.htm
SubhasisM 2014年

Answers:


47

/bin/sh如今在任何系统上几乎都不是Bourne外壳程序(即使Solaris是最后一个包含它的主要系统之一,现在也已在Solaris 11中将其/ bin / sh切换到POSIX sh)。/bin/sh是70年代初期的汤普森炮弹。1979年,Bourne shell在Unix V7中取代了它。

/bin/sh 此后多年一直是Bourne外壳程序(或Almquist外壳程序,BSD上的免费实现)。

如今,/bin/sh更常见的是POSIX sh语言的解释器或另一种解释器,它本身是基于ksh88语言的子集(以及一些不兼容的Bourne shell语言的超集)。

Bourne Shell或POSIX sh语言规范不支持数组。或者更确切地说,他们只有一个阵列:位置参数($1$2$@,所以每个功能一个阵列以及)。

ksh88确实有您设置的数组set -A,但是在POSIX sh中没有指定该数组,因为语法很尴尬并且不太实用。

与阵列/列表变量其他外壳包括:csh/ tcshrcesbash(其中大部分是复制的ksh的语法ksh93的方式), ,,yash 每一个不同的语法(Unix的的一次要被后继的壳,并且是最一致那些)...zshfishrcfishzsh

在标准中sh(也适用于Bourne shell的现代版本):

set '1st element' 2 3 # setting the array

set -- "$@" more # adding elements to the end of the array

shift 2 # removing elements (here 2) from the beginning of the array

printf '<%s>\n' "$@" # passing all the elements of the $@ array 
                     # as arguments to a command

for i do # looping over the  elements of the $@ array ($1, $2...)
  printf 'Looping over "%s"\n' "$i"
done

printf '%s\n' "$1" # accessing individual element of the array.
                   # up to the 9th only with the Bourne shell though
                   # (only the Bourne shell), and note that you need
                   # the braces (as in "${10}") past the 9th in other
                   # shells.

printf '%s\n' "$# elements in the array"

printf '%s\n' "$*" # join the elements of the array with the 
                   # first character (byte in some implementations)
                   # of $IFS (not in the Bourne shell where it's on
                   # space instead regardless of the value of $IFS)

(请注意,在Bourne shell和ksh88中,$IFS必须包含空格"$@"才能正常工作(错误),在Bourne shell中,您无法访问上面的元素$9${10}不会起作用,您仍然可以执行shift 1; echo "$9"或循环执行他们))。


2
非常感谢...您的详细解释非常有帮助。
SubhasisM 2014年

1
可能值得注意的是,在某些关键功能中,位置参数与bash数组不同。例如,它们不支持稀疏数组,并且由于sh没有切片参数扩展,因此您无法访问像的子列表"${@:2:4}"。可以肯定的是,我看到了相似之处,但是我并不认为位置参数本身就是一个数组
kojiro

@kojiro,在一定程度上,我会说这是相反的,"$@"就像一个数组(像的阵列cshrczshfishyash...),它更多了Korn / bash的“阵列”是不是真的数组,但有些键的键数组限制为正整数(它们的索引也从0开始,而不是像其他所有带数组和“ $ @”的shell一样从1开始)。支持切片的shell可以对$ @进行相同的切片(ksh93 / bash在切片“ $ @”时笨拙地向位置参数添加$ 0)。
斯特凡Chazelas

3

普通的Bourne外壳中没有数组。您可以使用以下方式创建数组并遍历它:

#!/bin/sh
# ARRAY.sh: example usage of arrays in Bourne Shell

array_traverse()
{
    for i in $(seq 1 $2)
    do
    current_value=$1$i
    echo $(eval echo \$$current_value)
    done
    return 1
}

ARRAY_1=one
ARRAY_2=two
ARRAY_3=333
array_traverse ARRAY_ 3

无论采用哪种方式使用数组,sh您都将选择它总是很麻烦。除非您只限于一个非常有限的平台或想学习一些东西,否则请考虑使用其他语言,例如Python或,Perl如果可以的话。


感谢您的回复...!实际上,我确实是在尝试在shell脚本中学习东西...否则,在Python中实现数组确实是小菜一碟。这是一个重要的教训,存在一些不支持数组的脚本语言:)一件事,您发布的代码给出了一个错误-“第6行出现语法错误:'$'意外” ...我有点忙现在,我会解决它的问题……请别打扰。
SubhasisM 2014年

@ NoobGeek,Bourne shell没有$(...)语法。因此,您确实必须拥有Bourne外壳。您是否在Solaris 10或更早版本上?您可能没有seq任何机会。在Solaris 10和更早版本上,您希望/ usr / xpg4 / bin / sh具有标准sh而非Bourne Shell。使用seq这种方式也不是很好。
斯特凡Chazelas

POSIX指出$和`在命令替换中是等效的:link。为什么使用seq这种方式不好?
Arkadiusz Drabczyk 2014年

2
是的,在POSIX shell中,您应该优先$(...)`,但是OP /bin/sh可能是Bourne shell,而不是POSIX shell。除了seq不是标准命令之外,$(seq 100)该操作还意味着将整个输出存储在内存中,这意味着它取决于包含换行符而不包含数字的$ IFS的当前值。最好使用i=1; while [ "$i" -le 100 ]; ...; i=$(($i + 1)); done(尽管在Bourne shell中也不起作用)。
斯特凡Chazelas

1
@Daenyth我要说的恰恰相反:首先学习bashism,然后学习可移植的/bin/sh语法,往往使人们认为使用错误的#!/bin/shshebang是可以的,然后在其他人尝试使用它们时破坏其脚本。强烈建议您不要发布这种烈性饵。:)
Josip Rodin 2015年

2

正如其他人所说的那样,Bourne Shell没有真正的数组。

但是,根据需要执行的操作,定界字符串就足够了:

sentence="I don't need arrays because I can use delimited strings"
for word in $sentence
do
  printf '%s\n' "$word"
done

如果典型的分隔符(空格,制表符和换行符)不够用,则可以IFS在循环之前设置为所需的任何分隔符。

而且,如果您需要以编程方式构建数组,则可以构建一个定界字符串。


1
除非您确实希望这样做(不太可能),否则您可能还希望禁用通配符,这是使变量不加引号的另一种效果(split+glob运算符)。
斯特凡Chazelas

0

一种模拟破折号的数组的方法(可以适应数组的任意数量的维):(请注意,使用该seq命令要求将IFS其设置为''(SPACE =默认值)。您可以使用while ... do ...do ... while ...循环而不是避免这种情况(我一直seq在更好地说明代码的作用)。

#!/bin/sh

## The following functions implement vectors (arrays) operations in dash:
## Definition of a vector <v>:
##      v_0 - variable that stores the number of elements of the vector
##      v_1..v_n, where n=v_0 - variables that store the values of the vector elements

VectorAddElementNext () {
# Vector Add Element Next
# Adds the string contained in variable $2 in the next element position (vector length + 1) in vector $1

    local elem_value
    local vector_length
    local elem_name

    eval elem_value=\"\$$2\"
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    vector_length=$(( vector_length + 1 ))
    elem_name=$1_$vector_length

    eval $elem_name=\"\$elem_value\"
    eval $1_0=$vector_length
}

VectorAddElementDVNext () {
# Vector Add Element Direct Value Next
# Adds the string $2 in the next element position (vector length + 1) in vector $1

    local elem_value
    local vector_length
    local elem_name

    eval elem_value="$2"
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    vector_length=$(( vector_length + 1 ))
    elem_name=$1_$vector_length

    eval $elem_name=\"\$elem_value\"
    eval $1_0=$vector_length
}

VectorAddElement () {
# Vector Add Element
# Adds the string contained in the variable $3 in the position contained in $2 (variable or direct value) in the vector $1

    local elem_value
    local elem_position
    local vector_length
    local elem_name

    eval elem_value=\"\$$3\"
    elem_position=$(($2))
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    if [ $elem_position -ge $vector_length ]; then
        vector_length=$elem_position
    fi

    elem_name=$1_$elem_position

    eval $elem_name=\"\$elem_value\"
    if [ ! $elem_position -eq 0 ]; then
        eval $1_0=$vector_length
    fi
}

VectorAddElementDV () {
# Vector Add Element
# Adds the string $3 in the position $2 (variable or direct value) in the vector $1

    local elem_value
    local elem_position
    local vector_length
    local elem_name

    eval elem_value="$3"
    elem_position=$(($2))
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    if [ $elem_position -ge $vector_length ]; then
        vector_length=$elem_position
    fi

    elem_name=$1_$elem_position

    eval $elem_name=\"\$elem_value\"
    if [ ! $elem_position -eq 0 ]; then
        eval $1_0=$vector_length
    fi
}

VectorPrint () {
# Vector Print
# Prints all the elements names and values of the vector $1 on sepparate lines

    local vector_length

    vector_length=$(($1_0))
    if [ "$vector_length" = "0" ]; then
        echo "Vector \"$1\" is empty!"
    else
        echo "Vector \"$1\":"
        for i in $(seq 1 $vector_length); do
            eval echo \"[$i]: \\\"\$$1\_$i\\\"\"
            ###OR: eval printf \'\%s\\\n\' \"[\$i]: \\\"\$$1\_$i\\\"\"
        done
    fi
}

VectorDestroy () {
# Vector Destroy
# Empties all the elements values of the vector $1

    local vector_length

    vector_length=$(($1_0))
    if [ ! "$vector_length" = "0" ]; then
        for i in $(seq 1 $vector_length); do
            unset $1_$i
        done
        unset $1_0
    fi
}

##################
### MAIN START ###
##################

## Setting vector 'params' with all the parameters received by the script:
for i in $(seq 1 $#); do
    eval param="\${$i}"
    VectorAddElementNext params param
done

# Printing the vector 'params':
VectorPrint params

read temp

## Setting vector 'params2' with the elements of the vector 'params' in reversed order:
if [ -n "$params_0" ]; then
    for i in $(seq 1 $params_0); do
        count=$((params_0-i+1))
        VectorAddElement params2 count params_$i
    done
fi

# Printing the vector 'params2':
VectorPrint params2

read temp

## Getting the values of 'params2'`s elements and printing them:
if [ -n "$params2_0" ]; then
    echo "Printing the elements of the vector 'params2':"
    for i in $(seq 1 $params2_0); do
        eval current_elem_value=\"\$params2\_$i\"
        echo "params2_$i=\"$current_elem_value\""
    done
else
    echo "Vector 'params2' is empty!"
fi

read temp

## Creating a two dimensional array ('a'):
for i in $(seq 1 10); do
    VectorAddElement a 0 i
    for j in $(seq 1 8); do
        value=$(( 8 * ( i - 1 ) + j ))
        VectorAddElementDV a_$i $j $value
    done
done

## Manually printing the two dimensional array ('a'):
echo "Printing the two-dimensional array 'a':"
if [ -n "$a_0" ]; then
    for i in $(seq 1 $a_0); do
        eval current_vector_lenght=\$a\_$i\_0
        if [ -n "$current_vector_lenght" ]; then
            for j in $(seq 1 $current_vector_lenght); do
                eval value=\"\$a\_$i\_$j\"
                printf "$value "
            done
        fi
        printf "\n"
    done
fi

################
### MAIN END ###
################

1
请注意,虽然local由两个支持bashdash,这不是POSIX。seq也不是POSIX命令。你或许应该提到的是你的代码,使上$ IFS的当前值的一些假设(如果您避免使用seq,并注明您的变量,它可以被避免)
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.