符合 POSIX 标准的 shell 中的数组

问题描述 投票:0回答:4

根据 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 的正确方法是什么?

arrays bash posix dash-shell
4个回答
30
投票

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

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


26
投票

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

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

这是一个示例脚本:

myArray="a b c d"

echo "$myArray" | tr ' ' '\n' | while read item; do
  # use '$item'
  echo $item
done

对此有一些更深入的解释:

  • tr ' ' '\n'
    可以让您在其中进行单字符替换 您删除空格并添加换行符 - 这是默认的分隔符 用于读取内置内容。

  • 当检测到 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

19
投票

确实,POSIX

sh
shell 没有与
bash
和其他 shell 相同的命名数组,但是 sh shell(以及
bash
和其他 shell)有一个
list
)可以使用,这就是位置参数的列表。

此列表通常包含传递给当前脚本或 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
对于将全局对象扩展为路径名列表也很有用:

#!/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 directory\n' "$#"

    echo 'These are:'
    printf '\t%s\n' "$@"
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 "$@"


3
投票

您可以在 POSIX shell 中将参数列表

$@
用作数组

初始化很简单,

shift
unshift
push

# 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 len=$#
for last in "$@"; do 
  if [ $((i+=1)) = 1 ]; then set --; fi  # increment $i. first run: empty $@
  if [ $i = $len ]; 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 "$@")
(如果您想操作
$@
并且不期望项目值中包含空格)。

如果需要处理item值中的空格,那就变得更麻烦了:

# 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

这仍然不适用于您的项目中的换行符。您必须将它们转义为另一个字符(但不为空),然后稍后将它们转义回来。请参阅上面的“迭代

$@
数组并修改其部分内容”。您可以在
for
循环中迭代数组,然后运行该函数,然后在
while IFS= read line
循环中修改变量,或者在不使用函数的
for
循环中完成所有操作。

© www.soinside.com 2019 - 2024. All rights reserved.