Shell 学习笔记 0x07

Shell 学习笔记 0x07

RayAlto OP

1. 数组

Bash 支持一维数组,比如保存 O 中比较 😭 UOHHHHH 的角色:

1
arr=(Yaoyao Nahida Dori Diona Sayu Klee Qiqi)

还可以:

1
arr=([0]=Yaoyao [1]=Nahida [2]=Dori [3]=Diona [4]=Sayu [5]=Klee [6]=Qiqi)

或者:

1
2
3
4
5
6
7
arr[0]=Yaoyao
arr[1]=Nahida
arr[2]=Dori
arr[3]=Diona
arr[4]=Sayu
arr[5]=Klee
arr[6]=Qiqi

访问元素可以用下标:

1
echo ${arr[5]}

btw 可莉酱找我聊天了捏

可莉来电的通话记录截图

正好是下午 6:48 打来的,但鼠鼠没钱捏,冲不起 648 ,最多冲个小月卡捏

robot sticker: cry

1.1. 访问数组的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ arr=("00 Yaoyao" "01 Nahida")
$ for i in ${arr[*]}; do echo $i; done
00
Yaoyao
01
Nahida
$ for i in ${arr[@]}; do echo $i; done
00
Yaoyao
01
Nahida
$ for i in "${arr[*]}"; do echo $i; done
00 Yaoyao 01 Nahida
$ for i in "${arr[@]}"; do echo $i; done
00 Yaoyao
01 Nahida

1.2. 数组里元素的个数

Bash 数组允许元素下标使用任何数字,比如:

1
arr[114514]="foo"

arr 数组只有一个元素,比如求数组元素个数:

1
2
$ echo ${#arr[@]}
1

不同于其他语言, Bash 不会对元素 0-114513 进行初始化,所以 Bash 也有获取数组有效下标的方法:

1
2
3
4
5
6
7
$ arr=([1]="foo" [14]="bar" [514]="baf")
$ echo ${!arr[@]}
1 14 514
$ for i in "${!arr[@]}"; do echo $i; done
1
14
514

1.3. 传递数组作为参数

Bash 不能传递数组,需要先 "${arr[@]}" 把数组变成参数字符串,然后用 $@ 接受这个字符串,也就是说变成了普通的位置参数,原来的下标也会失效:

1
2
3
4
5
6
7
8
9
function print_arr () {
for i in $@; do
echo $i
done
return 0
}

arr=([1]="foo" [14]="bar" [514]="baf")
print_arr "${arr[@]}"

1.4. 在数组末尾添加元素

通过 += Bash 可以自动把元素:

  • 如果 0 没有元素则放进 0 里
  • 否则放到末尾( 1 到最大下标之间的空位不会被填补)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function print_arr () {
for i in $@; do
echo $i
done
return 0
}

arr=([2]="Yaoyao" [3]="Nahida" [4]="Klee")
echo ${!arr[@]}
print_arr "${arr[@]}"
arr+="Diona"
echo ${!arr[@]}
print_arr "${arr[@]}"
arr+=("Qiqi" "Sayu")
echo ${!arr[@]}
print_arr "${arr[@]}"
arr+=([8]="Dori")
echo ${!arr[@]}
print_arr "${arr[@]}"

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2 3 4
Yaoyao
Nahida
Klee
0 2 3 4
Diona
Yaoyao
Nahida
Klee
0 2 3 4 5 6
Diona
Yaoyao
Nahida
Klee
Qiqi
Sayu
0 2 3 4 5 6 8
Diona
Yaoyao
Nahida
Klee
Qiqi
Sayu
Dori

1.5. 删除数组元素

删除一个变量可以用 unset

1
2
3
4
5
6
$ echo $foo
bar
$ unset foo
$ echo $foo

$

unset 也可以用来删除数组的某个元素:

1
2
3
4
5
6
$ arr=(foo bar baf)
$ echo ${arr[@]}
foo bar baf
$ unset arr[2]
$ echo ${arr[@]}
foo bar

或者整个数组:

1
2
3
4
5
6
7
$ arr=(foo bar baf)
$ echo ${arr[@]}
foo bar baf
$ unset arr
$ echo ${arr[@]}

$

但光是 var= 不能删除整个数组:

1
2
3
4
5
6
$ arr=(foo bar baf)
$ echo ${arr[@]}
foo bar baf
$ arr=
$ echo ${arr[@]}
foo bar baf

1.6. 数组排序

Bash 没有内置的排序,但可以借助 sort

1
2
3
4
5
6
7
8
function sort_arr () {
for i in $@; do echo $i; done | sort
}

arr=(f e d c b a)
echo "Original array: ${arr[@]}"
arr_sorted=($(sort_arr "${arr[@]}"))
echo "Sorted array: ${arr_sorted[@]}"

1.7. 关联数组

像普通数组相似,关联数组可以用字符串作为下标,类似 Python 的 dict ,关联数组必须用 declare -A 来声明:

1
2
3
4
5
declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"
echo ${colors["green"]}

2. 其他

2.1. Group/Subshell

1
2
3
ls /path/to/dir1 > files
ls /path/to/dir2 >> files
ls /path/to/dir3 >> file

这种可以用 group 来实现:

1
{ ls /path/to/dir1; ls /path/to/dir2; ls /path/to/dir3; } > files

{ } 包围,且要用空格与指令隔开,每个指令都要用 ; 结尾。或者用 subshell :

1
(ls /path/to/dir1; ls /path/to/dir2; ls /path/to/dir3) > files

( ) 包围,不需要用空格与指令隔开,最后一个指令也不需要加上 ; 。更重要的情况是使用 | 进行重定向:

1
{ ls /path/to/dir1; ls /path/to/dir2; ls /path/to/dir3; } | sort

group 和 subshell 的区别顾名思义: group 里面的指令都在同一个 shell 里完成,而使用 subshell 会创建子进程、复制出一个 subshell 环境来执行指令,执行后这个子进程会被销毁,里面的变量也会同时被销毁:

1
2
3
4
5
$ echo "bar" | { read foo; echo $foo; }
bar
$ echo $foo

$

| 右边读取 | 左边的输出、放进了变量 foo 里,且在 | 右边能正常读取 foo 变量,但指令结束后 foo 变量就被销毁了。一般情况下使用 group 占用资源更少、速度也更快

2.2. 进程替换

为了解决这个问题, Bash 有进程替换,一个用于产生标准输出 <(commands) 、一个用于接受标准输入 >(commands) ,比如上面的可以这样解决:

1
2
3
4
$ { read foo; echo $foo; } < <(echo "bar")
bar
$ echo $foo
bar

实际上 <(commands)commands 的输出转换成了一个文件:

1
2
$ echo <(echo "bar")
/dev/fd/63

2.3. trap

Bash 也可以捕获信号,类似 C 的 signal

1
2
3
4
5
6
trap "echo 'SIGINT, ignore'" SIGINT

for i in {1..5}; do
echo "$i..."
sleep 2
done

运行时会像这样:

1
2
3
4
5
6
7
1...
2...
^CSIGINT, ignore
3...
^CSIGINT, ignore
4...
5...

trap 也可以真的像 signal 一样使用函数作为参数:

1
2
3
4
5
6
function sigint_handler() {
echo "SIGINT, ignore"
return 0
}

trap sigint_handler SIGINT

2.4. 临时文件

*nix 下,临时文件一般被放在 /tmp 目录下,比如按照 ${程序名}.${PID}.${随机数}

1
tmp_file=/tmp/$(basename $0).$$.$RANDOM

$RANDOM 只能随机 1-32767 之间的整数,有一个更简单的程序可以升级&简化这个操作:

1
2
3
$ tmp_file=$(mktemp /tmp/$(basename $0).$$.XXXXXXXXXX)
$ echo $tmp_file
zsh.1908.HOMO114514

2.5. 异步执行

Bash 甚至有异步执行机制,但其实就是生成一个子进程但不阻塞父进程,然后可以等待子进程结束,其中子进程 PID 可以通过 $! 获得,比如子脚本 child.sh

1
2
3
4
5
#!/bin/sh

echo "Child: running..."
sleep 5
echo "Child: exiting."

和父脚本 parent.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh

echo "Parent: running..."
echo "Parent: swapning child process..."
# 子进程运行子脚本,不阻塞
$(dirname $0)/child.sh &
# 获取子进程 PID
child_pid=$!
echo "Parent: child (PID=$child_pid) spawned"
sleep 2
echo "Parent: waiting for child..."
# 等待子进程
wait $child_pid
echo "Parent: child finished, exiting"

运行父脚本:

1
2
3
4
5
6
7
Parent: running...
Parent: swapning child process...
Parent: child (PID=4624) spawned
Child: running...
Parent: waiting for child...
Child: exiting.
Parent: child finished, exiting

2.6. 命名管道

管道线 | 用的就是管道,用 mkfifo 可以创建一个命名管道,比如

1
mkfifo pipe

然后一边读取这个管道:

1
cat < pipe

另一边向管道输入内容:

1
echo "foo" > pipe

这样就模拟了:

1
echo "foo" | cat

未完待续

以后再来记录