根据 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 循环 + read 内置函数进行拆分。由于内置的破折号读取也不支持分隔符,因此您也必须解决这个问题。
这是一个示例脚本:
myArray="a b c d"
echo "$myArray" | tr ' ' '\n' | while read item; do
# use '$item'
echo $item
done
对此有一些更深入的解释:
tr ' ' '\n'
可以让您在其中进行单字符替换
您删除空格并添加换行符 - 这是默认的分隔符
用于读取内置内容。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)有一个 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 "$@"
您可以在 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
循环中完成所有操作。