Shell Notes

我的Shell脚本笔记

Posted by Ryan Yim on 2022-03-27
Estimated Reading Time 22 Minutes
Words 4.7k In Total
Viewed Times

Shell Notes

Linux shell脚本笔记

数据结构

数组应用

https://zhuanlan.zhihu.com/p/644793620 (https://itsfoss.com/bash-arrays/)

https://www.cnblogs.com/sparkdev/p/7152164.html

基础访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# 数组定义
# 隐式定义arr数组
arr[0]=apple
arr[1]=apple1
# 显式申明
declare -a arr
# 初始化
arr=(apple orange pear)
等同于declare -a arr=(apple orange pear)

# 数组长度
${#arr[@]}
# 数组中某个元素长度
arr=(a aaa aaaa)
# 3
echo ${#arr[1]}

# 访问数组元素
distros=(Ubuntu Fedora SUSE "Arch Linux" Nix)
# 格式:${array_name[N]}
# SUSE
echo ${distros[2]}
# 变量索引
i=2
echo ${distros[$i]}
# hello会被当作0处理
echo ${myArr[hello]}

# 追加数组元素
distros+=("Debian")
# 或者
distros=("${distros[@]}" Debian)

# 截取子数组
declare -a arr=(a b c d e f g)
# c d e 从index2开始截取3个
echo ${arr[@]:2:3}
a=(${arr[@]:1:3})

# 从index4开始的全部
${arr[@]:4}

# 连接两个数组
abc=(a b c)
distros=("${distros[@]}" "${abc[@]}")

# 打印数组列表 竖列
arrList=(1 2 3 4)
for ele in ${arrList[@]}
do
echo "$ele"
done
# 一行表达
for element in "${myArr[@]}"; do echo $element done
printlnArr (){
# printlnArr ${arr[*]}
for ele in $1
do
echo "$ele"
done
}

# 一行打印数组所有元素 ${array[*]}
# 1 2 3 4
echo ${distros[*]}
echo ${dupArr[@]}
printArr(){
# printArr ${distros[*]}
echo $1
}

# 复制传入的数组
test() {
echo "函数接收到的参数列表为:$@"
newarr=($*)
echo "新数组的值为:${newarr[@]}"
}
test ${dupArr[@]}

# 使用索引将元素设置在任何位置
array_name[N]=new_value
# 但请记住使用正确的索引编号。 如果在现有索引上使用它,新值将替换该元素。如果你使用“越界”索引,它仍会添加到最后一个元素之后。例如,如果数组长度为 6,并且你尝试在索引 9 处设置新值,则该值仍将作为最后一个元素添加到第 7 个位置(索引 6)。

# 删除数组元素 使用 Shell 内置的 unset 通过提供索引号来删除数组元素:
unset array_name[N]
# 通过 unset 来删除整个数组:
unset array_name

进阶运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 去除重复
# 去除重复
dupArr=(1 2 3 4 5 5 6 7 8 9 4 3 2 4 5 6 8)
# 去除dupArr中相同元素
dupArrlen=${#dupArr[@]}
for((i=0;i<$dupArrlen;i++))
do
for((j=$dupArrlen-1;j>i;j--))
do
if [[ ${dupArr[i]} = ${dupArr[j]} ]];then
unset dupArr[i]
fi
done
done
result=()
index=0
for each in ${dupArr[@]}
do
result[$index]=$each
index=$((index+1))
done
arrRemoveDup() {
# arrRemoveDup ${dupArr[*]}
# echo ${result[*]}
arrRemoveDupArr=($*)
arrRemoveDupArrlen=${#arrRemoveDupArr[@]}
for ((i = 0; i < $arrRemoveDupArrlen; i++)); do
for ((j = $arrRemoveDupArrlen - 1; j > i; j--)); do
if [[ ${arrRemoveDupArr[i]} = ${arrRemoveDupArr[j]} ]]; then
unset arrRemoveDupArr[i]
fi
done
done
result=()
index=0
for each in ${arrRemoveDupArr[@]}; do
result[$index]=$each
index=$((index + 1))
done
}

# 数组排序
# 仅能排序数字(if判断中"-gt" 改为 “-lt"可实现降序)
for ((a = 0; a < ${#mm[*]}; a++)); do
for ((k = $a + 1; k < ${#mm[*]}; k++)); do
if [ ${mm[$a]} -gt ${mm[$k]} ]; then
qq=${mm[$a]}
mm[$a]=${mm[$k]}
mm[$k]=$qq
fi
done
done
sortNumArr1() {
# sortNumArr ${toSort[*]} # bb=${mm[*]}
mm=($*)
for ((a = 0; a < ${#mm[*]}; a++)); do
for ((k = $a + 1; k < ${#mm[*]}; k++)); do
if [ ${mm[$a]} -gt ${mm[$k]} ]; then
qq=${mm[$a]}
mm[$a]=${mm[$k]}
mm[$k]=$qq
fi
done
done
}

# 仅能排序数字
sortNumArr2() {
# 定义函数 exchangeEle() 交换数组中两个元素的位置
exchangeEle() {
# 使用临时变量保存数组元素
local temp=${exchangeEleArr[$1]}
# 交换元素的位置
exchangeEleArr[$1]=${exchangeEleArr[$2]}
exchangeEleArr[$2]=$temp
return
}
# sortArrByStr ${aa[*]}
# echo ${rr[*]}
exchangeEleArr=($*)
# 通过 for 循环对数组排序,注意此时是以字符串来比较的
for ((last = ${#exchangeEleArr[@]}; last > 1; last--)); do
for ((i = 0; i < last - 1; i++)); do
[[ "${exchangeEleArr[$i]}" -gt "${exchangeEleArr[$((i+1))]}" ]] && exchangeEle $i $((i+1))
done
done
rr=${exchangeEleArr[*]}
}

# 根据字符串排序
sortArrByStr() {
# 定义函数 exchangeEle() 交换数组中两个元素的位置
exchangeEle() {
# 使用临时变量保存数组元素
local temp=${exchangeEleArr[$1]}
# 交换元素的位置
exchangeEleArr[$1]=${exchangeEleArr[$2]}
exchangeEleArr[$2]=$temp
return
}
# sortArrByStr ${aa[*]}
# echo ${rr[*]}
exchangeEleArr=($*)
# 通过 for 循环对数组排序,注意此时是以字符串来比较的
for ((last = ${#exchangeEleArr[@]}; last > 1; last--)); do
for ((i = 0; i < last - 1; i++)); do
[[ "${exchangeEleArr[$i]}" > "${exchangeEleArr[$((i + 1))]}" ]] && exchangeEle $i $((i + 1))
done
done
rr=${exchangeEleArr[*]}
}

字符串处理

分割字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash  
# Example to split a string using trim (tr) command
strTosplit="We;welcome;you;on;yiibai!"
strArr=($(echo "$strTosplit" | tr ";" "\n"))

for i in "${my_arr[@]}"
do
echo $i
done

# 更多请阅读:https://www.yiibai.com/bash/bash-split-string.html
#Example for bash split string by space

read -p "Enter any string separated by space: " str #reading string value

IFS=' ' #setting space as delimiter
read -ra ADDR <<<"$str" #reading str as an array as tokens separated by IFS

for i in "${ADDR[@]}"; #accessing each element of array
do
echo "$i"
done

提取等号前面字段 只能一个符号
echo "name=usr" | cut -d '=' -f 1
提取xxx字符串后面的
echo "name.se rusr" | sed 's/.xxx.*//'

字符串转数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 字符串转数组:以“;”为分隔符分隔的数组
listToArr="a;b;v;x"
OLD_IFS="$IFS"
# 以“;”为分隔符
IFS=";"
gotArr=($listToArr)
IFS="$OLD_IFS"
echo "分割后的数组长度是: ${#gotArr[@]}"
for ele in ${gotArr[@]}
do
echo "$ele"
done

# 函数
strToArr="a;b;v;x"
strSplitor=";"
strSplit () {
OLD_IFS="$IFS"
# 以“;”为分隔符
IFS="$strSplitor"
gotArr=($1)
IFS="$OLD_IFS"
echo "分割后的数组长度是: ${#gotArr[@]}"
for ele in ${gotArr[@]}
do
echo "$ele"
done
}
strSplit "$strToArr"

Sed处理文本

(见Why-Linux文章)

查找文本文件中是否存在某字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查找文件中的字符串
check_var="export PATH=\"\$PATH:/usr/sbin\""
echo $check_var
if cat './new.txt' | grep "$check_var" > /dev/null
then
echo "域名为 $check_var 已存在"
echo "newexport PATH=\"\$PATH:/usr/sbin\"" >> ./new.txt
else
echo "未找到"
fi

if sudo cat '/etc/network/interfaces.d/setup' | grep "$check_var" > /dev/null
then
prompt -w "有"
else
prompt -m "无"
fi

条件判断

环境判断

判断文件夹、文件是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 判断文件存在
fileToCheck=""
if [ -f "$fileToCheck" ];then
echo "File "$fileToCheck" exist."
else
echo "File "$fileToCheck" not found."
fi

# 判断文件夹存在 注意:"~/.config"的表达无效
pathToCheck=""
if [ -d "$pathToCheck" ];then
echo "文件夹存在"
else
echo "文件夹不存在"
fi

# 否定直接加!
if [ ! -f "$pathToCheck" ]; then
touch "$pathToCheck"
fi

判断是否安装某命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 方法1
cmdToCheck="git"
if ! [ -x "$(command -v "$cmdToCheck")" ]; then
echo "Error: "$cmdToCheck" is not installed." >&2
else
echo "Command found! : "$cmdToCheck"" >&1
fi

# 方法2
cmdToCheck="node"
if ! type "$cmdToCheck" >/dev/null 2>&1; then
echo "Error: "$cmdToCheck" is not installed.";
else
echo "Command found! : "$cmdToCheck"" >&1
fi

输入判断

判断Yes、No

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# 方法1:
print_comfirm () {
flag=true
while $flag
do
read -r -p "Sure to send to printer? [Y/n] " input
case $input in [yY][eE][sS]|[yY])
mv $filepath .
prompt -s "正在打印...."
lp $file
flag=false
;;
[nN][oO]|[nN])
exit 0
flag=false
;;
*)
prompt -w "Invalid input..."
;;
esac
done
}
# 模板:
echo -en "\033[1;32mAfter send to printer, file'll be renamed to \033[1;31m“ $filerename ” \033[1;33m"
if read -t 5 -p "Sure to send to printer ? (Y/n) " input
then
if [ -z "${input}" ];then
input='y'
fi
else
prompt -e "\n\nERROR: Timeout, quit[1]. Canceled and released !"
exit 1
fi
case $input in [yY][eE][sS]|[yY])
echo "Yes"
;;
[nN][oO]|[nN])
exit 0
flag=false
;;
*)
prompt -w "Invalid input..."
;;
esac

# 复制这个
comfirm () {
flag=true
ask=$1
while $flag
do
read -r -p "$ask [y/N] " input
if [ -z "${input}" ];then
# 默认选择N
input='n'
fi
case $input in [yY][eE][sS]|[yY])
return 1
flag=false
;;
[nN][oO]|[nN])
return 2
flag=false
;;
*)
prompt -w "Invalid option..."
;;
esac
done
}

comfirm "问题描述"
choice=$?
if [ $choice == 1 ];then
echo "Y"
elif [ $choice == 2 ];then
echo "N"
else
echo "未知返回值"
exit 5
fi

# 方法二:
YES=(Y y)
read -r res
for i in ${YES[@]}
do
if [ "$i" == "$res" ]
then
echo "Yes"
else
echo "NO"
fi
done

# 方法三:简单的确认
echo -n "(y/n)"
read -r res
if [ $res == 'y' ]
then
echo "Yes"
else
echo "Else"
exit 1
fi

# 设置延时
if read -t 5 -p "please enter your name:" name
then
echo "hello $name, welcome to my script"
else
echo "sorry,too slow"
fi

判断字符串是否为数字

https://pythonjishu.com/ebwtgxhewebkbgo/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 正则表达式判断 注意:输入不准为空!
toCheck=""
if echo "$toCheck" | grep -q "^[0-9]*$"; then
echo "String "$toCheck" is a number"
else
echo "String "$toCheck" is not a number"
fi

# 使用echo命令进行求值 不可判断空输入!
toCheck=""
if [ "$(echo "$toCheck" | tr -d '[:digit:]')" = "" ]; then
echo "String "$toCheck" is a number"
else
echo "String "$toCheck" is not a number"
fi

# 使用sed (使用sed命令将字符串中的非数字字符替换为空,然后比较替换前后的字符串长度是否一致,如果一致,则原始字符串为纯数字)
# 不可判断空输入!
toCheck=""
if [ "$(echo "$toCheck" | sed 's/[0-9]//g')" = "" ]; then
echo "String "$toCheck" is a number"
else
echo "String "$toCheck" is not a number"
fi

# 使用awk进行判断(使用awk命令对字符串进行求值,如果能够正确求值,则原始字符串为数字)
# 可判断空输入
toCheck=""
echo "$toCheck" | awk '{if ($0 ~ /^[0-9]+$/) printf(""$toCheck" is a number\n"); else printf(""$toCheck" is not a number\n")}'

# 使用expr进行判断(使用expr命令对字符串进行求值,如果能够求值则原始字符串为数字,否则会得到一个错误提示)
# 可判断空输入
toCheck=""
if [ $(expr "$toCheck" + 0 2>/dev/null) ]; then
echo "String "$toCheck" is a number"
else
echo "String "$toCheck" is not a number"
fi

# 使用BASH中的正式表达式
# 可判断空输入
toCheck=""
bash_re='^[0-9]+$'
if [[ $toCheck =~ $bash_re ]]; then
echo "String "$toCheck" is a number"
else
echo "String "$toCheck" is not a number"
fi

判断root密码是否正确以及免密码su

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 注意,下面缩进不能更改!
# 以root身份运行, 代码来源:https://jimolonely.github.io/2019/04/15/linux/022-su-in-script/
ROOT_PASSWD="123456"
doAsRoot () {
su - root <<! >/dev/null 2>&1
$ROOT_PASSWD
echo " Exec $1 as root"
$1
!
}

# 检查root密码是否正确
# 待检测的密码
ROOT_PASSWD="123456"
# 检查root密码是否正确
checkRootPasswd () {
# >/dev/null是不显示到标准输出
# 2>&1 错误重定向到标准输出
su - root <<! >/dev/null 2>/dev/null
$ROOT_PASSWD
# 下面是要执行的命令
pwd
!

echo "$ROOT_PASSWD" | sudo -S cmd

数学运算

整数计算

这里的只能整数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 加减法
num=0
num=$((num+1))
total=$(($f1 + $f2))
sub=$((10-3))

# 乘法 除法
MEGA=$(($GIGA * 1024))
div=$((20 / 4))

# 幂运算符**
a=$1
b=$2
result=$((a**b))

# 计算余数
rem=$((17%5))

浮点数计算

1
2
3
4
5
# 浮点数计算:https://www.jb51.cc/bash/392906.html
# 会得到0,因为只支持整数
RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))
# 浮点数应该这样
echo "scale=2; ${userinput}" | bc

标准输出 - Std.out

打印带颜色的文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 方案1:
echo命令打印带有色彩的文字:
文字色: echo -e "\e[1;31mThis is red text\e[0m"
This is red text?\e[1;31m 将颜色设置为红色
?\e[0m 将颜色重新置回
颜色码:重置=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,洋红=35,青色=36,白色=37
echo -e "\e[1;42mGreed Background\e[0m"
Greed Background颜色码:重置=0,黑色=40,红色=41,绿色=42,黄色=43,蓝色=44,洋红=45,青色=46,白色=47
文字闪动:echo -e "\033[37;31;5mMySQL Server Stop...\033[39;49;0m"
红色数字处还有其他数字参数:0 关闭所有属性、1 设置高亮度(加粗)、4 下划线、5 闪烁、7 反显、8 消隐
echo -e "\e[1;32m \e[0m"

# 方案2:推荐
# 颜色colors
CDEF=" \033[0m" # default color
CCIN=" \033[0;36m" # info color
CGSC=" \033[0;32m" # success color
CRER=" \033[0;31m" # error color
CWAR=" \033[0;33m" # warning color
b_CDEF=" \033[1;37m" # bold default color
b_CCIN=" \033[1;36m" # bold info color
b_CGSC=" \033[1;32m" # bold success color
b_CRER=" \033[1;31m" # bold error color
b_CWAR=" \033[1;33m"

# echo like ... with flag type and display message colors
# -s 绿
# -e 红
# -w 黄
# -i 蓝
prompt () {
case ${1} in
"-s"|"--success")
echo -e "${b_CGSC}${@/-s/}${CDEF}";; # print success message
"-e"|"--error")
echo -e "${b_CRER}${@/-e/}${CDEF}";; # print error message
"-w"|"--warning")
echo -e "${b_CWAR}${@/-w/}${CDEF}";; # print warning message
"-i"|"--info")
echo -e "${b_CCIN}${@/-i/}${CDEF}";; # print info message
*)
echo -e "$@"
;;
esac
}

系统信息获取

获取显示设备信息

https://segmentfault.com/a/1190000039239040

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 获取屏幕分辨率1600 x 900 
# Screen 0: minimum 8 x 8, current 1600 x 900, maximum 32767 x 32767
xrandr | grep -o "current [0-9]* x [0-9]*" | sed -e 's/current *//g'
# 分别获取
SCREEN=$(xrandr | grep -o "current [0-9]* x [0-9]*" | sed -e 's/current *//g')
SCREEN_W=$(echo $SCREEN | sed -e 's/ x [0-9]*//')
SCREEN_H=$(echo $SCREEN | sed -e 's/[0-9]* x //')

在屏幕上关闭一个窗口的时候,有时会想起一句古老的诗,人生天地间,忽如远行客。我要写的截屏工具,也许能留住任一窗口璀璨的瞬间,只要我知道它在哪里。一个窗口在哪里,是由它的左上角坐标以及它的宽度和高度决定的。在我还没发现 X 窗口系统的 xwininfo 这个工具之前,我只能考虑使用诗歌来实现这样的截屏工具……xwininfo 说,我知道你想要知道的,只要你用鼠标点一下你想点的……
$ xwininfo
xwininfo: Please select the window about which you
would like information by clicking the
mouse in that window.
declare -a WIN_PARAMS
WIN_PARAMS=($(xwininfo | sed -n -e '/^[[:space:]]*Absolute ..*[XY]/p;
/^[[:space:]]*Relative ..*[XY]/p;
/^[[:space:]]*Width:/p;
/^[[:space:]]*Height:/p' | awk 'BEGIN{FS=":"}{print $2}'))

获取剪贴板

1
2
3
4
5
6
复制(到 gnome 的剪贴板)
xsel < file.txt
cat file.txt | xsel

粘贴
xsel > file.txt

获取文件夹内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/bin/bash
# http://t.zoukankan.com/yuandaozhe-p-14519296.html
# 直接循环处理文件
function doFiles(){
for file in `ls $1` #注意此处这是两个反引号,表示运行系统命令
do
if [ -d $1"/"$file ] #注意此处之间一定要加上空格,否则会报错
then
echo "Msg: Directory "$1"/'$file' Passed."
else
if [[ "$file" == *"$2" ]];
then
echo "Change this line to do sth. --> $file"
fi
fi
done
}

# 直接循环处理文件(递归)
function doFilesRecur(){
for file in `ls $1`
do
if [ -d $1"/"$file ]
then
doFilesRecur $1"/"$file $2
else
if [[ "$file" == *"$2" ]];
then
echo "Change this line to do sth. --> $file"
fi
fi
done
}


# 返回gf_list文件列表
function getFiles(){
for file in `ls $1`
do
if [ -d $1"/"$file ]
then
echo "Msg: Directory "$1"/'$file' Passed."
else
if [[ "$file" == *"$2" ]];
then
# 未验证
file="$1"/"$file"
file_list=$file_list$file
fi
fi
done
OLD_IFS="$IFS"
IFS="♨"
gf_list=($file_list)
IFS="$OLD_IFS"
# 未验证
gfr_list=(${gfr_list[@]})
}

# 返回gfr_list文件列表(递归)
function getFilesRecur(){
for file in `ls $1`
do
if [ -d $1"/"$file ]
then
getFilesRecur $1"/"$file $2
else
if [[ "$file" == *"$2" ]];
then
file="$1"/"$file"
file_list=$file_list$file
fi
fi
done
OLD_IFS="$IFS"
IFS="♨"
gfr_list=($file_list)
IFS="$OLD_IFS"
gfr_list=(${gfr_list[@]})
}


# 用法:目录+扩展名
doFilesRecur "." "mp4"
getFiles "." "mp4"
# 打印列表
for each in ${gf_list[@]}
do
echo "$each"
done

时间日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
参考:https://www.cnblogs.com/howhy/p/5999762.html
当前时间:date "+%Y%m%d%H%M%S"
20220212202614
比如今日是2012-04-22
$ date -d "+1 day" +%Y-%m-%d
2012-04-23
$ date -d "2012-04-10 +1 day " +%Y-%m-%d
2012-04-11
$ date -d "-1 year " +%Y-%m-%d
2011-04-22

# 2021-02-19 23:32:18
date +"%Y-%m-%d %T"
# 2021-02-19-23-34-47
date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g"

其他

Gnome-Terminal运行脚本时打开窗口

1
gnome-terminal -- 【脚本路径】

管道

1
2
3
4
5
6
实践中的一个具体例子
让我们详细描述从外壳运行复杂命令(管道)时会发生什么。我们假设我们有一个bash过程(在Debian的标准用户的shell),以PID 4374; 在此外壳中,我们键入命令:ls | sort
shell首先解释键入的命令。在我们的例子中,它理解有两个程序(lssort),数据流从一个程序流向另一个程序(由|字符表示,称为pipe)。bash首先创建一个未命名的管道(该管道最初仅存在于bash流程本身中)。
然后,shell克隆自己;这就导致了一个新的bash进程,PID #4521(PID的是抽象的数字,一般没有特别的意思)。进程#4521继承了管道,这意味着它能够在其“输入”侧进行写入;bash将其标准输出流重定向到此管道的输入。然后,它执行(并用其替换)该ls程序,该程序列出了当前目录的内容。由于ls在其标准输出上进行写入,并且此输出先前已被重定向,因此结果将有效地发送到管道中。
第二个命令发生类似的操作:bash再次克隆自身,导致bash进程号为pid#4522的新进程。由于它也是#4374的子进程,因此它也继承了管道。bash然后将其标准输入连接到管道输出,然后执行(并用其替换)sort命令,该命令对输入进行排序并显示结果。
难题的所有部分都已设置好:ls读取当前目录并将文件列表写入管道;sort读取此列表,按字母顺序对其进行排序,然后显示结果。然后,进程#4521和#4522终止,并且#4374(在操作过程中正在等待它们),恢复控制并显示提示,以允许用户键入新命令。

多行注释

1
2
3
4
5
6
:<<!
for each in ${APP_NAME[@]}
do
echo "$each"
done
!

其他

1
2
3
4
5
6
7
# shell处理apt source文件:
sudo echo "" > /etc/apt/sources.list是无效的
应该使用:echo "" | sudo tee /etc/apt/sources.list

# 获取php版本号
systemctl list-unit-files | grep "\-"fpm | grep ^php | sed 's/.fpm.*//'
systemctl list-unit-files | grep "\-"fpm | grep ^php | sed 's/.fpm.*//' | sed 's/php//g'

示例

屏幕截图/录屏脚本

https://segmentfault.com/a/1190000039239040

截屏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash
NAME=$(date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g")
IMAGE=/tmp/${NAME}.png
# echo $IMAGE
# 获取分辨率
SCREEN=$(xrandr | grep -o "current [0-9]* x [0-9]*" | sed -e 's/current *//g')
SCREEN_W=$(echo $SCREEN | sed -e 's/ x [0-9]*//')
SCREEN_H=$(echo $SCREEN | sed -e 's/[0-9]* x //')
# 获取窗口几何
declare -a WIN_PARAMS
WIN_PARAMS=($(xwininfo | sed -n -e '/^[[:space:]]*Absolute ..*[XY]/p;
/^[[:space:]]*Relative ..*[XY]/p;
/^[[:space:]]*Width:/p;
/^[[:space:]]*Height:/p' | awk 'BEGIN{FS=":"}{print $2}'))
# 构造理想中的截图区。
# 之所以如此,与 xwininfo 输出的窗口左上角相对坐标有关。
# MARGIN=${WIN_PARAMS[2]}
MARGIN=12
WIN_X=$((${WIN_PARAMS[0]} - ${WIN_PARAMS[2]}))
WIN_Y=$((${WIN_PARAMS[1]} - ${WIN_PARAMS[3]}))
WIN_W=$((${WIN_PARAMS[4]} + ${WIN_PARAMS[2]} + $MARGIN))
WIN_H=$((${WIN_PARAMS[5]} + ${WIN_PARAMS[3]} + $MARGIN))

# 截图区越界处理
if (($WIN_X < 0)); then WIN_X=0; fi
if (($WIN_Y < 0)); then WIN_Y=0; fi
if (($WIN_W + $WIN_X > $SCREEN_W)); then WIN_W=$(($SCREEN_W - $WIN_X)); fi
if (($WIN_H + $WIN_Y > $SCREEN_H)); then WIN_H=$(($SCREEN_H - $WIN_Y)); fi
ffmpeg -video_size ${WIN_W}x${WIN_H} \
-f x11grab -ss 00:00:00 \
-i :0.0+${WIN_X},${WIN_Y} \
-frames:v 1 $IMAGE 2>/dev/null
gimp $IMAGE &

录屏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/bash
NAME=$(date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g")
RECORD=/tmp/${NAME}.mkv
OUTPUT=/tmp/output-${NAME}.mkv

# 获取屏幕分辨率
SCREEN=$(xrandr | grep -o "current [0-9]* x [0-9]*" | sed -e 's/current *//g')
SCREEN_W=$(echo $SCREEN | sed -e 's/ x [0-9]*//')
SCREEN_H=$(echo $SCREEN | sed -e 's/[0-9]* x //')

# 获取窗口几何参数
declare -a WIN_PARAMS
WIN_PARAMS=($(xwininfo | sed -n -e '/^[[:space:]]*Absolute ..*[XY]/p;
/^[[:space:]]*Relative ..*[XY]/p;
/^[[:space:]]*Width:/p;
/^[[:space:]]*Height:/p' | awk 'BEGIN{FS=":"}{print $2}'))

# 构造理想中的截图区。
# 之所以如此,与 xwininfo 输出的窗口左上角相对坐标有关。
MARGIN=${WIN_PARAMS[2]}
WIN_X=$((${WIN_PARAMS[0]} - ${WIN_PARAMS[2]}))
WIN_Y=$((${WIN_PARAMS[1]} - ${WIN_PARAMS[3]}))
WIN_W=$((${WIN_PARAMS[4]} + ${WIN_PARAMS[2]} + $MARGIN))
WIN_H=$((${WIN_PARAMS[5]} + ${WIN_PARAMS[3]} + $MARGIN))

# 截图区越界处理
if (($WIN_X < 0)); then WIN_X=0; fi
if (($WIN_Y < 0)); then WIN_Y=0; fi
if (($WIN_W + $WIN_X > $SCREEN_W)); then WIN_W=$(($SCREEN_W - $WIN_X)); fi
if (($WIN_H + $WIN_Y > $SCREEN_H)); then WIN_H=$(($SCREEN_H - $WIN_Y)); fi

# 录制指定窗口区域
ffmpeg -video_size ${WIN_W}x${WIN_H} \
-framerate 25 -f x11grab \
-i :0.0+${WIN_X},${WIN_Y} \
-c:v libx264rgb -crf 0 -preset ultrafast $RECORD

# 视频后处理
ffmpeg -i $RECORD -c:v libx264rgb -crf 0 -preset veryslow $OUTPUT

If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !