当前位置: 首页 > news >正文

shell展开shell数组

shell展开&shell数组

参数展开

大多数的参数展开会用在脚本中,而不是命令行中。

基本参数

The simplest form of parameter expansion is reflected in the ordinary use of variables.

最简单的参数展开形式反映在平常使用的变量上。例如:$a,当$a 展开后,会变成变量 a 所包含的值。简单参数也可能用花括号引起来:${a}

$ a="foo"
$ echo "$a_file"

如果我们执行这个序列,没有任何输出结果,因为 shell 会试着展开一个称为 a_file 的变量,而不是 a。通过 添加花括号可以解决这个问题:

$ echo "${a}_file"
foo_file

我们已经知道通过把数字包裹在花括号中,可以访问大于9的位置参数。例如,访问第十一个位置参数,我们可以这样做:${11}

管理空变量的展开

${parameter:-word}

若 parameter 没有设置(例如,不存在)或者为空,展开结果是 word 的值。若 parameter 不为空,则展开结果是 parameter 的值。

$ foo=
$ echo ${foo:-"substitute value if unset"}
substitute value if unset
$ echo $foo
$ foo=bar
$ echo ${foo:-"substitute value if unset"}
bar
$ echo $foo
bar

${parameter:=word}

若 parameter 没有设置或为空,展开结果是 word 的值。另外,word 的值会赋值给 parameter。 若 parameter 不为空,展开结果是 parameter 的值。

$ foo=
$ echo ${foo:="default value if unset"}
default value if unset
$ echo $foo
default value if unset
$ foo=bar
$ echo ${foo:="default value if unset"}
bar
$ echo $foo
bar

${parameter:?word}

若 parameter 没有设置或为空,这种展开导致脚本带有错误退出,并且 word 的内容会发送到标准错误。若 parameter 不为空, 展开结果是 parameter 的值。

$ foo=
$ echo ${foo:?"parameter is empty"}
bash: foo: parameter is empty
$ echo $?
1
$ foo=bar
$ echo ${foo:?"parameter is empty"}
bar
$ echo $?
0

${parameter:+word}

若 parameter 没有设置或为空,展开结果为空。若 parameter 不为空, 展开结果是 word 的值会替换掉 parameter 的值;然而,parameter 的值不会改变。

$ foo=
$ echo ${foo:+"substitute value if set"}
$ foo=bar
$ echo ${foo:+"substitute value if set"}
substitute value if set

返回变量名的参数展开

${!prefix*}

${!prefix@}

这种展开会返回以 prefix 开头的已有变量名。

$ echo ${!BASH*}
BASH BASH_ARGC BASH_ARGV BASH_COMMAND BASH_COMPLETION
BASH_COMPLETION_DIR BASH_LINENO BASH_SOURCE BASH_SUBSHELL
BASH_VERSINFO BASH_VERSION

字符串展开

${#parameter}

展开成由 parameter 所包含的字符串的长度。通常,parameter 是一个字符串;然而,如果 parameter 是 @ 或者是 * 的话, 则展开结果是位置参数的个数。

$ foo="This string is long."
$ echo "'$foo' is ${#foo} characters long."
'This string is long.' is 20 characters long.

${parameter:offset}

${parameter:offset:length}

这些展开用来从 parameter 所包含的字符串中提取一部分字符。提取的字符始于第 offset 个字符(从字符串开头算起)直到字符串的末尾,除非指定提取的长度。

$ foo="This string is long."
$ echo ${foo:5}
string is long.
$ echo ${foo:5:6}
string

若 offset 的值为负数,则认为 offset 值是从字符串的末尾开始算起,而不是从开头。注意负数前面必须有一个空格, 为防止与 ${parameter:-word} 展开形式混淆。length,若出现,则必须不能小于零。

如果 parameter 是 @,展开结果是 length 个位置参数,从第 offset 个位置参数开始。

$ foo="This string is long."
$ echo ${foo: -5}
long.
$ echo ${foo: -5:2}
lo

${parameter#pattern}

${parameter##pattern}

这些展开会从 paramter 所包含的字符串中清除开头一部分文本,这些字符要匹配定义的 pattern。pattern 是 通配符模式,就如那些用在路径名展开中的模式。这两种形式的差异之处是该 # 形式清除最短的匹配结果, 而该 ## 模式清除最长的匹配结果。

$ foo=file.txt.zip
$ echo ${foo#*.}
txt.zip
$ echo ${foo##*.}
zip

${parameter%pattern}

${parameter%%pattern}

这些展开和上面的 # 和 ## 展开一样,除了它们清除的文本从 parameter 所包含字符串的末尾开始,而不是开头。

$ foo=file.txt.zip
$ echo ${foo%.*}
file.txt
$ echo ${foo%%.*}
file

${parameter/pattern/string}

${parameter//pattern/string}

${parameter/#pattern/string}

${parameter/%pattern/string}

这种形式的展开对 parameter 的内容执行查找和替换操作。如果找到了匹配通配符 pattern 的文本, 则用 string 的内容替换它。在正常形式下,只有第一个匹配项会被替换掉。在该 // 形式下,所有的匹配项都会被替换掉。 该 /# 要求匹配项出现在字符串的开头,而 /% 要求匹配项出现在字符串的末尾。/string 可能会省略掉,这样会 导致删除匹配的文本。

$ foo=JPG.JPG
$ echo ${foo/JPG/jpg}
jpg.JPG
$ echo ${foo//JPG/jpg}
jpg.jpg
$ echo ${foo/#JPG/jpg}
jpg.JPG
$ echo ${foo/%JPG/jpg}
JPG.jpg
#!/bin/bash
# 用参数展开 ${#j} 取代命令 $(echo $j | wc -c)
for i; doif [[ -r $i ]]; thenmax_word=max_len=for j in $(strings $i); dolen=${#j}if (( len > max_len )); thenmax_len=$lenmax_word=$jfidoneecho "$i: '$max_word' ($max_len characters)"fishift
done

下一步,使用 time 命令来比较这两个脚本版本的效率:

$ time longest-word2 dirlist-usr-bin.txt
dirlist-usr-bin.txt: 'scrollkeeper-get-extended-content-list' (38
characters)
real 0m3.618s
user 0m1.544s
sys 0m1.768s
$ time longest-word3 dirlist-usr-bin.txt
dirlist-usr-bin.txt: 'scrollkeeper-get-extended-content-list' (38
characters)
real 0m0.060s
user 0m0.056s
sys 0m0.008s

大小写转换

在我们试图查询数据库之前,把用户的输入转换成标准化。 通过把用户输入的字符全部转换成小写字母或大写字母,并且确保数据库中的条目 按同样的方式规范化。

declare 命令可以用来把字符串规范成大写或小写字符。

#!/bin/bash
declare -u upper
declare -l lower
if [[ $1 ]]; thenupper="$1"lower="$1"echo $upperecho $lower
fi

使用 declare 命令来创建两个变量,upper 和 lower。

把第一个命令行参数的值(位置参数1)赋给每一个变量,然后把变量值在屏幕上显示出来:

$ test aBc
ABC
abc

大小写转换参数展开

格式 结果
$ 把 parameter 的值全部展开成小写字母。
$ 仅仅把 parameter 的第一个字符展开成小写字母。
$ 把 parameter 的值全部转换成大写字母。
$ 仅仅把 parameter 的第一个字符转换成大写字母(首字母大写)。
#!/bin/bash 
if [[ $1 ]]; thenecho ${1,,}echo ${1,}echo ${1^^}echo ${1^}
fi

[me@linuxbox ~]$ ul-param aBc
abc
aBc
ABC
ABc

算术求值和展开

算术展开了。它被用来对整数执行各种算术运算。它的基本格式是:

$((expression))

这里的 expression 是一个有效的算术表达式。

数基

在算术表达式中,shell 支持任意进制的整型常量。

指定不同的数基

表示法 描述
number 默认情况下,没有任何表示法的数字被看做是十进制数(以10为底)。
0number 在算术表达式中,以零开头的数字被认为是八进制数。
0xnumber 十六进制表示法
base#number number 以 base 为底
$ echo $((0xff))
255
$ echo $((2#11111111))
255

简单算术

Since the shell’s arithmetic only operates on integers, the results of division are always whole numbers:

因为 shell 算术只操作整型,所以除法运算的结果总是整数:

$ echo $(( 5 / 2 ))
2
$ echo $(( 5 % 2 ))
1

通过使用除法和取模运算符,我们能够确定5除以2得数是2,余数是1。

在循环中计算余数是很有用处的。在循环执行期间,它允许某一个操作在指定的间隔内执行。在下面的例子中, 我们显示一行数字,并高亮显示5的倍数:

#!/bin/bash
for ((i = 0; i <= 20; i = i + 1)); doremainder=$((i % 5))if (( remainder == 0 )); thenprintf "<%d> " $ielseprintf "%d " $ifi
done
printf "\n"
$ modulo
<0> 1 2 3 4 <5> 6 7 8 9 <10> 11 12 13 14 <15> 16 17 18 19 <20>

赋值运算符

=+=-=++--

test 命令接受单个 = 运算符 来测试字符串等价性。这也是使用更现代的 [[ ]] 和 (( )) 复合命令来代替 test 命令的另一个原因。

$ foo=1
$ echo $((++foo))
2
$ echo $foo
2

自增 ++ 和 自减 -- 运算符经常和循环操作结合使用。我们将改进我们的 modulo 脚本,让代码更紧凑些:

#!/bin/bash
for ((i = 0; i <= 20; ++i )); doif (((i % 5) == 0 )); thenprintf "<%d> " $ielseprintf "%d " $ifi
done
printf "\n"

位运算符

被用在某类底层的任务中, 经常涉及到设置或读取位标志。

位运算符

运算符 描述
~ 按位取反。对一个数字所有位取反。
<< 位左移. 把一个数字的所有位向左移动。
>> 位右移. 把一个数字的所有位向右移动。
& 位与。对两个数字的所有位执行一个 AND 操作。
^ 位异或。对两个数字的所有位执行一个异或操作。

注意除了按位取反运算符之外,其它所有位运算符都有相对应的赋值运算符(例如,<<=)。

这里我们将演示产生2的幂列表的操作,使用位左移运算符:

[me@linuxbox ~]$ for ((i=0;i<8;++i)); do echo $((1<<i)); done
1
2
4
8
16
32
64
128

逻辑运算符

operators. 复合命令 (( )) 支持各种各样的比较运算符。还有一些可以用来计算逻辑运算。

比较运算符

运算符 描述
<= 小于或相等
>= 大于或相等
< 小于
> 大于
== 相等
!= 不相等
&& 逻辑与
expr1?expr2:expr3 条件(三元)运算符。若表达式 expr1 的计算结果为非零值(算术真),则 执行表达式 expr2,否则执行表达式 expr3。

表达式的计算结果是零,则认为假,而非零表达式认为真。 该 (( )) 复合命令把结果映射成 shell 正常的退出码:

$ if ((1)); then echo "true"; else echo "false"; fi
true
$ if ((0)); then echo "true"; else echo "false"; fi
false
$ a=0
$ ((a<1?a+=1:a-=1))
bash: ((: a<1?a+=1:a-=1: attempted assignment to non-variable (error token is "-=1")

通过把赋值表达式用括号括起来,可以解决这个错误:

$ ((a<1?(a+=1):(a-=1)))

下一步,我们看一个使用算术运算符更完备的例子,该示例产生一个简单的数字表格:

#!/bin/bash
finished=0
a=0
printf "a\ta**2\ta**3\n"
printf "=\t====\t====\n"
until ((finished)); dob=$((a**2))c=$((a**3))printf "%d\t%d\t%d\n" $a $b $c((a<10?++a:(finished=1)))
done

在这个脚本中,我们基于变量 finished 的值实现了一个 until 循环。首先,把变量 finished 的值设为零(算术假), 继续执行循环之道它的值变为非零。在循环体内,我们计算计数器 a 的平方和立方。在循环末尾,计算计数器变量 a 的值。 若它小于10(最大迭代次数),则 a 的值加1,否则给变量 finished 赋值为1,使得变量 finished 算术为真, 从而终止循环。运行该脚本得到这样的结果:

$ arith-loop
a    a**2     a**3
=    ====     ====
0    0        0
1    1        1
2    4        8
3    9        27
4    16       64
5    25       125
6    36       216
7    49       343
8    64       512
9    81       729
10   100      1000

bc - 一种高精度计算器语言

不能直接用 shell 完成更高级的数学运算或仅使用浮点数。为此,我们需要使用外部程序。

嵌入的 Perl 或者 AWK 程序是一种可能的方案,

另一种方式就是使用一种专业的计算器程序。这样一个程序叫做 bc,在大多数 Linux 系统中都可以找到。bc 程序读取一个用它自己的类似于 C 语言的语法编写的脚本文件。一个 bc 脚本可能是一个分离的文件或者是从标准输入读入。bc 语言支持相当少的功能,包括变量,循环和程序员定义的函数。

编写一个 bc 脚本来执行2加2运算:

/* A very simple bc script */
2 + 2

使用 bc

如果我们把上面的 bc 脚本保存为 foo.bc,然后我们就能这样运行它:

$ bc foo.bc
bc 1.06.94
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software
Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
4

可以通过 -q(quiet)选项禁止这些版权信息。

bc 也能够交互使用:

$ bc -q
2 + 2
4
quit

bc 的 quit 命令结束交互会话。

也可能通过标准输入把一个脚本传递给 bc 程序:

$ bc < foo.bc
4

这种接受标准输入的能力,意味着可以使用 here 文档,here字符串,和管道来传递脚本。这里是一个使用 here 字符串的例子:

[me@linuxbox ~]$ bc <<< "2+2"
4

一个脚本实例

计算每月的还贷金额。使用了 here 文档把一个脚本传递给 bc:

#!/bin/bash
PROGNAME=$(basename $0)
usage () {cat <<- EOFUsage: $PROGNAME PRINCIPAL INTEREST MONTHSWhere:PRINCIPAL is the amount of the loan.INTEREST is the APR as a number (7% = 0.07).MONTHS is the length of the loan's term.EOF
}
if (($# != 3)); thenusageexit 1
fi
principal=$1
interest=$2
months=$3
bc <<- EOFscale = 10i = $interest / 12p = $principaln = $monthsa = p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1))print a, "\n"
EOF
$ loan-calc 135000 0.0775 180
475
1270.7222490000

若贷款 135,000 美金,年利率为 7.75%,借贷180个月(15年),这个例子计算出每月需要还贷的金额。 注意这个答案的精确度。这是由脚本中变量 scale 的值决定的。

创建一个数组

数组变量就像其它 bash 变量一样命名,当被访问的时候,它们会被自动地创建。

$ a[1]=foo
$ echo ${a[1]}
foo

通过第一个命令,把数组 a 的元素1赋值为 “foo”。 第二个命令显示存储在元素1中的值。在第二个命令中使用花括号是必需的, 以便防止 shell 试图对数组元素名执行路径名展开操作。

也可以用 declare 命令创建一个数组:

$ declare -a a

使用 -a 选项,declare 命令的这个例子创建了数组 a。

数组赋值

name[subscript]=value
name=(value1 value2 ...)
$ days=(Sun Mon Tue Wed Thu Fri Sat)
$ days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)

访问数组元素

脚本用来检查一个特定目录中文件的修改次数。显示这些文件最后是在一天中的哪个小时被修改的。这样一个脚本 可以被用来确定什么时段一个系统最活跃。这个脚本,称为 hours,输出这样的结果:

$ hours .
Hour Files Hour Files
---- ----- ---- ----
00   0     12   11
01   1     13   7
02   0     14   1
03   0     15   7
04   1     16   6
04   1     17   5
06   6     18   4
07   3     19   4
08   1     20   1
09   14    21   0
10   2     22   0
11   5     23   0
Total files = 80

当执行该 hours 程序时,指定当前目录作为目标目录。它打印出一张表显示一天(0-23小时)每小时内, 有多少文件做了最后修改。

#!/bin/bash
usage () {echo "usage: $(basename $0) directory" >&2
}
# Check that argument is a directory
if [[ ! -d $1 ]]; thenusageexit 1
fi
# Initialize array
for i in {0..23}; do hours[i]=0; done
# Collect data
for i in $(stat -c %y "$1"/* | cut -c 12-13); doj=${i/#0}((++hours[j]))((++count))
done
# Display data
echo -e "Hour\tFiles\tHour\tFiles"
echo -e "----\t-----\t----\t-----"
for i in {0..11}; doj=$((i + 12))printf "%02d\t%d\t%02d\t%d\n" $i ${hours[i]} $j ${hours[j]}
done
printf "\nTotal files = %d\n" $count

这个脚本由一个函数(名为 usage),和一个分为四个区块的主体组成。

第一部分,我们检查是否有一个命令行参数, 且该参数为目录。如果不是目录,会显示脚本使用信息并退出。

第二部分初始化一个名为 hours 的数组。给每一个数组元素赋值一个0。虽然没有特殊需要在使用之前准备数组,但是我们的脚本需要确保没有元素是空值。注意这个循环构建方式很有趣。通过使用花括号展开({0..23}),我们能很容易为 for 命令产生一系列的数据(words)。

接下来的一部分收集数据,对目录中的每一个文件运行 stat 程序。我们使用 cut 命令从结果中抽取两位数字的小时字段。 在循环里面,我们需要把小时字段开头的零清除掉,因为 shell 将试图(最终会失败)把从 “00” 到 “09” 的数值解释为八进制。 下一步,我们以小时为数组索引,来增加其对应的数组元素的值。最后,我们增加一个计数器的值(count),记录目录中总共的文件数目。

脚本的最后一部分显示数组中的内容。我们首先输出两行标题,然后进入一个循环产生两栏输出。最后,输出总共的文件数目。

数组操作

输出整个数组的内容

下标 *@ 可以被用来访问数组中的每一个元素。

$ animals=("a dog" "a cat" "a fish")
$ for i in ${animals[*]}; do echo $i; done
a
dog
a
cat
a
fish
$ for i in ${animals[@]}; do echo $i; done
a
dog
a
cat
a
fish
$ for i in "${animals[*]}"; do echo $i; done
a dog a cat a fish
$ for i in "${animals[@]}"; do echo $i; done
a dog
a cat
a fish

${animals[*]}${animals[@]}的行为是一致的直到它们被用引号引起来。

确定数组元素个数

$ a[100]=foo
$ echo ${#a[@]} # number of array elements
1
$ echo ${#a[100]} # length of element 100
3

把字符串赋值给数组元素100, bash 仅仅报告数组中有一个元素。

这不同于一些其它语言的行为,这种行为是数组中未使用的元素(元素0-99)会初始化为空值, 并把它们计入数组长度。

找到数组使用的下标

${!array[*]}

${!array[@]}

这里的 array 是一个数组变量的名字。和其它使用符号 * 和 @ 的展开一样,用引号引起来的 @ 格式是最有用的, 因为它能展开成分离的词。

$ foo=([2]=a [4]=b [6]=c)
$ for i in "${foo[@]}"; do echo $i; done
a
b
c
$ for i in "${!foo[@]}"; do echo $i; done
2
4
6

在数组末尾添加元素

如果我们需要在数组末尾附加数据,那么知道数组中元素的个数是没用的,因为通过 * 和 @ 表示法返回的数值不能 告诉我们使用的最大数组索引。

shell 为我们提供了一种解决方案。通过使用+=赋值运算符, 我们能够自动地把值附加到数组末尾。这里,我们把三个值赋给数组 foo,然后附加另外三个。

$ foo=(a b c)
$ echo ${foo[@]}
a b c
$ foo+=(d e f)
$ echo ${foo[@]}
a b c d e f

数组排序

#!/bin/bash
a=(f e d c b a)
echo "Original array: ${a[@]}"
a_sorted=($(for i in "${a[@]}"; do echo $i; done | sort))
echo "Sorted array: ${a_sorted[@]}"
$ array-sort
Original array: f e d c b a
Sorted array:
a b c d e f

删除数组

$ foo=(a b c d e f)
$ echo ${foo[@]}
a b c d e f
$ unset foo
$ echo ${foo[@]}
$

也可以使用 unset 命令删除单个的数组元素:

$ foo=(a b c d e f)
$ echo ${foo[@]}
a b c d e f
$ unset 'foo[2]'
$ echo ${foo[@]}
a b d e f

注意数组元素必须 用引号引起来为的是防止 shell 执行路径名展开操作。

有趣地是,给一个数组赋空值不会清空数组内容:

$ foo=(a b c d e f)
$ foo=
$ echo ${foo[@]}
b c d e f

任何没有下标的对数组变量的引用都指向数组元素0:

$ foo=(a b c d e f)
$ echo ${foo[@]}
a b c d e f
$ foo=A
$ echo ${foo[@]}
A b c d e f

关联数组

现在最新的 bash 版本支持关联数组了。关联数组使用字符串而不是整数作为数组索引。 这种功能给出了一种有趣的新方法来管理数据。例如,我们可以创建一个叫做 “colors” 的数组,并用颜色名字作为索引。

declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"

不同于整数索引的数组,关联数组必须用带有 -A 选项的 declare 命令创建。

echo ${colors["blue"]}
http://www.hskmm.com/?act=detail&tid=29551

相关文章:

  • shell排错
  • 原木
  • 格式化输出与文本处理
  • 2025年10月镀锌卷板厂家最新推荐排行榜,有花镀锌卷板,无花镀锌卷板,高锌层镀锌卷板,批发镀锌卷板公司推荐
  • React 19.2 重磅更新!这几个新特性终于来了
  • Akka.NET高性能分布式Actor框架完全指南
  • 基于Docker搭建MySQL Cluster
  • 2025 年抗氧剂厂家最新推荐排行榜,聚酯防黄变抗氧剂,透明膜防晶点抗氧剂,PC聚碳防黄变抗氧剂公司推荐!
  • PaddleX服务化部署精度低于命令行调用的原因及解决方案 - 指南
  • 某中心与华盛顿大学公布机器人研究奖项与学者名单
  • 会话跟踪方案
  • 阻塞、非阻塞、同步、异步的区别是什么?
  • 如何防范员工泄露数据给 AI?2025年选型与落地实战版
  • Linux文本编辑三剑客之grep
  • Linux文本编辑三剑客之sed
  • 做了项目经理才发现:上台发言,其实都有套路
  • 占位符
  • 什么是IO多路复用?
  • 进程、线程和协程之间的区别和联系
  • 挣点小钱的副业(附带新手教程)0元的快乐
  • Linux文本编辑三剑客之awk
  • 软考~高效的系统规划与管理师考试—知识篇—V2.0—第四章 IT 服务规划设计 — 2017 年 2018 年 2020 年 2022 年 2023 年
  • 10.12
  • 从“优化工具”到“价值生态”:多价值主体系统如何重塑AI价值对齐范式
  • 2.2 深度学习(Deep Learning)
  • 结对项目
  • 第十二篇
  • 2.1 函数逼近(Function Approximation)
  • Elasticsearch 备份:snapshot 镜像使用篇
  • 本次科研收获