符合 POSIX 标准的外壳中的数组



根据 hyperpolyglot.org 上的参考表,可以使用以下语法来设置数组。

i=(1 2 3)

但是我收到一个错误 dash,这是 Ubuntu 上/bin/sh的默认值,应该符合 POSIX 标准。

# Trying the syntax with dash in my terminal
> dash -i
$ i=(1 2 3)
dash: 1: Syntax error: "(" unexpected
$ exit
# Working fine with bash
> bash -i
$ i=(1 2 3)
$ echo ${i[@]}
1 2 3
$ exit

参考表是否具有误导性或错误性?
如果是,定义数组或列表并符合 POSIX 的正确方法是什么?

Posix 不指定数组,因此如果您仅限于 Posix shell 功能,则无法使用数组。

恐怕你的参考是错误的。可悲的是,并非您在互联网上找到的所有内容都是正确的。

正如 rici 所说,dash 没有数组支持。但是,如果您要做的是编写循环,则有一些解决方法。

For 循环不会做数组,但您可以使用 while 循环 + 内置读取进行拆分。由于内置的破折号读取也不支持分隔符,因此您也必须解决此问题。

下面是一个示例脚本:

myArray="a b c d"
echo "$myArray" | tr ' ' 'n' | while read item; do
  # use '$item'
  echo $item
done

对此有一些更深层次的解释:

  • tr ' ' 'n'将允许您在何处执行单字符替换您删除空格并添加换行符 - 这是默认的 delim用于内置读取。

  • 当检测到 stdin 时,read将以失败的退出代码退出已关闭 - 这将是当您的输入已完全完成时处理。

  • 由于 echo 在其输入后打印一个额外的换行符,这将让您处理数组中的最后一个"元素"。

这相当于 bash 代码:

myArray=(a b c d)
for item in ${myArray[@]}; do
  echo $item
done

如果要检索第 n 个元素(出于示例的目的,假设第 2 个元素(:

myArray="a b c d"
echo $myArray | cut -d  -f2 # change -f2 to -fn

的确,POSIX sh shell 没有像 bash 和其他 shell 那样具有命名数组,但是有一个列表可供 sh shell(以及 bash 和其他(使用,这就是位置参数列表。

此列表通常包含传递给当前脚本或 shell 函数的参数,但您可以使用 set 内置命令设置其值:

#!/bin/sh
set -- this is "a list" of "several strings"

在上面的脚本中,位置参数 $1$2 、 ...,设置为所示的五个字符串。--用于确保您不会意外设置 shell 选项(set 命令也可以执行此操作(。 不过,只有当第一个参数以-开头时,这才是一个问题。

例如,要遍历这些字符串,您可以使用

for string in "$@"; do
    printf 'Got the string "%s"n' "$string"
done

或较短的

for string do
    printf 'Got the string "%s"n' "$string"
done

或者只是

printf 'Got the string "%s"n' "$@"

set对于将 glob 扩展到路径名列表也很有用:

#!/bin/sh
set -- "$HOME"/*/
# "visible directory" below really means "visible directory, or visible 
# symbolic link to a directory".
if [ ! -d "$1" ]; then
    echo 'You do not have any visible directories in your home directory'
else
    printf 'There are %d visible directories in your home directoryn' "$#"
    echo 'These are:'
    printf 't%sn' "$@"
fi

shift内置命令可用于从列表中移出第一个位置参数。

#!/bin/sh
# pathnames
set -- path/name/1 path/name/2 some/other/pathname
# insert "--exclude=" in front of each
for pathname do
    shift
    set -- "$@" --exclude="$pathname"
done
# call some command with our list of command line options
some_command "$@"

您可以在 POSIX shell 中使用参数列表$@作为数组

初始化、shiftunshiftpush是微不足道的:

# initialize $@ containing a string, a variable's value, and a glob's matches
set -- "item 1" "$variable" *.wav
# shift (remove first item, accepts a numeric argument to remove more)
shift
# unshift (prepend new first item)
set -- "new item" "$@"
# push (append new last item)
set -- "$@" "new item"

下面是一个pop实现:

# pop (remove last item, store it in $last)
i=0
for last in "$@"; do 
  if [ $((i+=1)) = 1 ]; then set --; fi  # increment $i. first run: empty $@
  if [ $i = $# ]; then break; fi         # stop before processing the last item
  set -- "$@" "$last"                    # add $a back to $@
done
echo "$last has been removed from ($*)"

($*$@的内容与默认为空格字符的$IFS连接起来。

循环访问$@数组并修改其某些内容:

i=0
for a in "$@"; do 
  if [ $((i+=1)) = 1 ]; then set --; fi  # increment $i. first run: empty $@
  a="${a%.*}.mp3"       # example tweak to $a: change extension to .mp3
  set -- "$@" "$a"      # add $a back to $@
done

引用 $@ 数组中的项:

echo "$1 is the first item"
echo "$# is the length of the array"
echo "all items in the array (properly quoted): $@"
echo "all items in the array (in a string): $*"
[ "$n" -ge 0 ] && eval "echo "the ${n}th item in the array is $$n""

(eval很危险,所以我在运行它之前$n确保它是一个数字(

有几种方法可以将$last设置为列表的最终项而不弹出它:
带有一个函数:

last_item() { shift $(($# - 1)) 2>/dev/null && printf %s "$1"; }
last="$(last_item "$@")"

。或带有eval(安全,因为$#始终是一个数字(:

eval last="$$#"

。或循环:

for last in "$@"; do true; done

⚠️ 警告:函数有自己的$@数组。您必须将其传递给函数,例如my_function "$@"如果是只读的,或者如果要操作$@并且不需要项值中的空格,则set -- $(my_function "$@")

如果需要处理项目值中的空格,则会变得更加麻烦:

# ensure my_function() returns each list item on its own line
i=1
my_function "$@" |while IFS= read line; do
  if [ $i = 1 ]; then unset i; set --; fi
  set -- "$@" "$line"
done

这仍然不适用于项目中的换行符。您必须将它们转义为另一个字符(但不是 null(,然后再将它们转义回来。请参阅上面的"循环访问$@数组并修改其某些内容"。您可以在 for 循环中循环访问数组,然后运行该函数,然后在while IFS= read line循环中修改变量,或者只是在没有函数的情况下在 for 循环中完成所有操作。

最新更新