根据 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 中使用参数列表$@
作为数组
初始化、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
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
循环中完成所有操作。