| aliases | |||||||
|---|---|---|---|---|---|---|---|
| tags |
|
||||||
| created | 2023-08-18 12:44:52 -0700 | ||||||
| modified | 2025-11-28 02:33:19 -0800 |
Bourne shell 由 Steve Bourne 在 AT&T 贝尔实验室开发,被认为是第一个 UNIX shell。它被表示为 sh。 #sh
GNU Bourne-Again Shell (bash) 更多被称为 Bash shell,它被设计成与 Bourne shell 兼容。Bash shell 融合了 Linux 中不同类型 shell 的有用功能,如 Korn shell 和 C shell。 #bash
Z Shell 或 zsh 是 sh shell 的扩展,在自定义方面做了大量改进。如果你想要一个具有更多功能的现代 shell,zsh shell 就是你要找的。 #zsh
Linux 下常见有:.bashrc、.profile、.bash_profile 等配置文件。
这些配置文件的区别,首先得从 shell 的 4 种使用模式讲起。
顾名思义就是 shell 与用户存在交互行为。 #shell/mode
与 交互模式 的情况就正好相反。 #shell/mode
使用 echo $0 可以检查是否是登录 shell:
- 如果显示结果是 shell 名称前有一个「连字符」
-,即登录 shell。 - 如果没有「连字符」,即非登录 shell。
要登录的 shell,无论是否交互,最后都得加载 .profile 文件。
而如果是
示例:
使用 ssh 连接本机:
$ ssh silascript@192.168.0.20
# silascript @ (base) in ~ [4:57:15]
$ echo $-
569XZhilms
# silascript @ (base) in ~ [4:57:25]
$ echo $0
-zshNote
可以看到 -zsh,这是有连字符 - 的,说明此时的 shell 是一个 登录shell。
另外,$- 的结果是 569XZhilms,其中存在 i,证明这个 shell 还是个 交互shell。
[!tip] 而如果只是直接通过本地的各种终端直接输入
echo $0,一般都是直接显示 shell 的路径,如/bin/zsh这样的。echo $-结果与通过 ssh 方式连接结果一致。由此可知,在图形界面的 Linux 下,打开一个终端窗口,这种方式的 shell 都是一个非登录式的 shell。
-
登录式 shell:
- 正常通过某终端登录的 shell。
- su - username。
- su -l username。
-
非登录式 shell:
- su username。
- 图形终端下打开的命令窗口。
- 自动执行的 shell 脚本。
Sell 语法相关的内容请查看: Shell 笔记
LinuxShell 可执行的命令有 3 种:
- 内建命令
- Shell函数
- 外部命令
这些命令集成在 Shell 解释器中,一种是改变 Shell 本身的属性设置;另一种是 I/O 命令,如 echo 命令。
使用 type 命令可以判断命令是内建命令还是 外部命令。
$ type cd
cd is a shell builtin内建命令会显示「builtin」字样。
是独立于 Shell 的可执行程序,如 find、grep 等。
对于外部命令,Shell 会创建一个新的进程来执行命令。
外部命令执行过程:
- 调用 POSIX 系统的
fork函数接口,创建珍上命令行 Shell 进程的复制(子进程) - 在子进程中,寻找外部命令
- 在子进程中,执行寻找到的外部命令,此时父进程牌休眠,等待子进程执行完毕
- 子进程执行完毕,父进程接着执行下一条命令
Tip
使用 source 执行 Shell 脚本时,不会创建子进程,而是在父进程中直接执行。所以要修改当前 Shell 本身环境变量,是使用 source 命令。
命名规则:
shell 对于空格有严格的规定:
- 赋值语句等号两边绝对不能有空格。
- 字符串比较 等号两边必须有空格。
- 所赋的值包含空格,可以用引号括起来。
if语句的[]中,表达式前后都应有空格。而if语句中那个;与]间不能有空格。[!example] 小示例
if [ "${s1}" = "hello" ];then
[!info] 相关资料
数字可带可不带引号,但不带引号的数字可计算,带引号的数字不能用于计算。
[!info] 相关资料
- 被双引号(
" ")括起来的变量替换是不会被阻止的, 所以双引号被称为「部分引用」,或「弱引用」。 - 被单引号(
' ')括号起来的变量替换会被禁止,变量名只会被解释成字面的意思,不会发生变量替换,所以单引号被称为「全引用」,或「强引用」。
默认在 shell 脚本中定义的变量都是「全局」(global)的。
使用 local 关键字修饰的变量,称为「局部变量」,其作用域只在 函数 范围。
如果出现同名,shell 函数中定义的 local 变量会屏蔽掉脚本定义的全局变量。
$0:当前脚本的文件名$n:传递给脚本或函数的参数。n是数字,表示第几个参数,从1开始。$#:传递给脚本或函数的参数个数。$*:传递给脚本或函数的所有参数,即以一个单字符显示所有向脚本传递的参数。$?:上个命令的退出状态,或函数的返回值。0表示没有错误,其他值表明有错误。$$:当前 Shell 进程 ID。$!:后台运行的最后一个进程 ID 号$@:与$*相同,但是使用时加引号,并在引号中返回每个参数。$-:显示 Shell 使用的当前选项,与set命令功能相同。
变量使用时,在变量名前加上 $。
示例:
local num1=5
echo $num1
Tip
$变量名 这种形式实质是 ${变量名} 的「简写形式」。在某些情况还 $变量名 这种形式可能引起错误,这时就需要用到 ${变量名} 这种标准形式。
[!info]
Shell 的变量只有使用或引用时,才需要加
$,声明、定义或赋值时,是不需要加$,这与 PHP的变量定义规则 不同。在没有
$时的变量,有以下几种情况:
- 变量被声明或赋值,如上面的示例
local num1=5- 变量被
unset- 变量被
export
语法:[ expression ]
括号中的表达式前后都有空格。
[!tip] shell 的空格
因为 Sehll 是命令加选项或参数而构成的,而命令与选项或参数间是以空格间隔的,所以在 Shell 中空格是有点「微妙」的存在。
在条件表达式中,括号中的表达式前后必须都加上空格,不然就会报错。而赋值表达式中,
=前后就不能加空格。
if [[ xxx ]];then
xxx
elif [[ xxx ]];then
xxxx
else
xxxx
fiTip
Shell 中是 elif,不是 else if
-eq或equal:等于-ne或not equal:不等于-gt或greate than:大于-lt或lesser than:小于-ge或greate or equal:大于等于-le或lesser or equal:小于等于
==:等于。[ "a" == "a" ]!=:不等于。[ "a" != "a"]-n:字符长度不等于 0 为真。[ -n "xxx" ]-z:字符串长度等于 0 为真。[ -z "xxx" ]
Tip
使用 -n 判断字符串长度时,变量加双引号。不但 -n,只要是字符串比较,都加双引号,这是一个好习惯。
-
-e:文件或目录存在为真。[ -e path ] -
-f:文件存在为真。 -
-d:目录存在为真。[!info] 示例
if [[ ! -d "/data/" ]];then mkdir /data fi
-
-r:有读权限为真。 -
-w:有写权限为真。 -
-x:有执行权限为真。
!:取反。[ ! $a -eq $b ]-a:和,类似于其他语言中的&运算符。[ 1 -eq 1 -a 2 -eq 2]-o:或,类似于其他语言中的|运算符。[ 1 -eq 1 -o 2 -eq 2]
&&:断路与,与其他语言一样,前后俩表达式都为 true,其最终结果才为 true;如果前面的表达式为 false,那不用判断后面的表达式,最终结果即为 false。||:断路或,与其他语言一样,前后俩表达式至少有一为 true,结果才为 true;如果前面表达式为 true,则不用判断后面的表达式。
Tip
如果当前 Shell 不支持逻辑判断符,可使用 布尔运算符 替代。
# 显示当前路径
echo $PWDTip
$PWD,跟 echo 命令一起使用时,必须大写。
使用 basename 命令可以得到一个包含后缀名的文件名。
示例:
$ basename ~/MyNotes/ITNotes/常用字体.txt
常用字体.txt-s:不显示指定的后缀名。
示例:
$ basename -s .txt ~/MyNotes/ITNotes/常用字体.txt
常用字体当然 -s 也可以省略,而指定的不显示的后缀名作为第二个参数:
$ basename ~/MyNotes/ITNotes/常用字体.txt .txt
常用字体甚至这个指定的后缀名不一定是后缀名,而是指定的任务结尾的字符,它本质只是在结果的末尾去掉匹配的字符串:
$ basename ~/MyNotes/ITNotes/常用字体.txt t
常用字体.tx使用 dirname,可以得到文件所在的目录路径字符串。
示例:
$ dirname ~/MyNotes/ITNotes/常用字体.txt
/home/silascript/MyNotes/ITNotes列表实际就是有空格的字符串。
构建一个列表:
list1=("aa" "bb" 5 "cc")获取列表元素,从 0 开始:
echo ${list1[0]}获取所有列表元素:
echo ${list1[@]}遍历列表:
for s_temp in "${list1[@]}"; do
echo $s_temp
done获取列表长度:
echo ${#list1[@]}数组名=()-
*:全部元素,是作为一个整体处理 -
@:同上,但会强制单词分隔[!tip] for in 中使用
*与@区别在for in循环中会体现出区别:arr1=("hello world" "the man") for i in "${arr1[*]}";do echo $i done ``` -
这段代码会输出
hello world then manfor i in "${arr1[@]}";do echo $i done
这段代码会输出:
hello world the man
取最后一个元素:arr[-1]。
[!tip] 老语法
${#arr[@]-1}:${#arr[@]}这是取数组长度(这跟 列表 是一样的。),那数组长度 -1 就得到数组最后一个元素的索引值。
${!arr[*]} 或 ${!arr[@]} 是查看已赋值元素的下标。
[!info]
@跟*的区别:
- 变量使用
*时,变量被""包裹,会当成一串字符串处理。- 变量使用
@时,变量被""包裹,依然当做数组处理。- 变量在没有被
""包裹的情况下,@跟*是等效的.
# 地址数组
addrs_arr=()
# 读取文件
# 去除空行及以 # 起头的行
for line in `cat $addrsls_file_path | grep -v ^$ | grep -v ^\#`
do
# 将数据存放到数组
addrs_arr+=("$line")
done
[!info]
grep -v表示反向选择。
^$与^\#都是 正则表达式 的东西。^$表示空行,^\#表示以#符号开始的行。
grep -v ^$ | grep -v ^\#表示就是选项非空行及非使用#「标记」的行。
方式 1:
# 遍历数组
for arr_temp in ${addrs_arr[*]}
do
echo $arr_temp
done
# 或者写成这样
for arr_temp in ${addrs_arr[@]}
do
echo $arr_temp
done
[!tip] 遍历说明
arr_temp是一个「临时变量」,用来存储每次循环从数组中取出的数据。这示例中的
for循环类似其他高级编程语言中的foreach语句的用法。
方式 2:
for i in ${!json_data_arr[@]}
do
echo ${json_data_arr[$i]}
doneTip
使用 ! 这种方式,是使用数组索引来遍历。
i 是数组索引
向数组中添加元素,可以有四种方法:
- 按照下标进行单个添加
array_name[index]=value- 在不做任何删减时,直接使用数组长度追加元素
array_name[${#array_name[@]}]=value- 直接获取源数组的全部元素再加上新要添加的元素,一并重新赋予该数组,重新刷新定义索引
array_name=("${#array_name[@]}" value1 value2 ... valueN)[!info]
双引号不能省略,否则数组中存在包含空格的元素时会按空格将元素拆分成多个。
不能将
@替换为*,如果替换为*,不加双引号时与@的表现一致,加双引号时,会将数组 array_name 中的所有元素作为一个元素添加到数组中。这规则在 字符串 中也同样适合。
- 使用
+=直接添加,待添加元素必须用()包围起来,并且多个元素用空格分隔
array_name+=(value1 value2 ... valueN)Important
待添加元素必须用 () 包围起来,并且多个元素用空格分隔
() 对于数组而言,是构建数组的关键。如获取一相函数返回的数组,如果没有 (),将被 Shell 当成字符串处理,这时使用 ${#array[@]} 来获取数组长度将只会是 1,因为虽然函数使用 echo ${array[@]} 返回了一个数组,但接收方会将这货当成一个字符串处理,因为 Shell 中实际都是字符,因为 Shell 或 Linux 大部分工具,实质是都是针对处理文本而生的,所以字符或字符串才是其本质。
function test1(){
local array1=(2 5 22 32 18)
echo ${array1[@]}
}
# 获取返回值次构建为数组
r_arr=($(test1))
echo ${#r_arr[@]}语法:unset 数组[引索]
fun1 ${ads_arr[*]}[!tip] 数组实参
在调用一个函数时,向此函数传入一个「实参」时,如果是普通变量只需要
$变量名,就可以。但如果要向函数传个数组,那语法就得「变下」,得写成
${数组名[*]}或${数组名[@]}。如果数组变量按普通变量写法传参,那只会传入数组第一个元素,即${数组名[0]}。其实这是符合数组获取 数组所有元素 的语法规则的。简单说,shell 语法与其他高级编程语言有点不一样,传参得「实打实」地传入数组的「所有元素」。
# 接收外部传来的数组
local ads_array=($@)[!tip] 语法解释
函数中接收传入的数组,其语法也与接收普通变量不一样,函数内接收普通变量参数可以这样:
temp=$数字,通过数字来指定接收哪个一参数。而因为数组不是一个数据,而是「一堆」数据,所以得使用特殊一点语法接收,其中
@跟 数组所有元素 的语法保持一致,另外,那对小括号(),其实是就是构建一个数组的语法。即ads_array=()这个是一个 数组初始化 语法。也就是说,函数内接收数组,其实是初始化了一个数组用来接收。
需求:第 2 个参数是一个数组,函数如何接收:
方案一:先接收第一个参数,然后使用 shift 命令用于实现实现位置参数左移,
语法格式:shift [n]
[!info]
说明:
shift命令用来删除参数。shift命令参数默认为 1,表示 从命令行删除第一个参数。当指定了参数 n 时,shift命令就一次删除 n 个参数。
# 定义函数
my_function() {
local first_arg="$1"
# 移除第一个参数
shift
# 剩余的参数就是数组元素
local array=("$@")
echo "第一个参数: $first_arg"
echo "数组元素:"
for element in "${array[@]}"; do
echo " $element"
done
}
# 调用函数
my_function "hello" "apple" "banana" "cherry"# 定义函数
my_function() {
local first_arg="$1"
local array_string="$2"
# 将字符串转换回数组
IFS=',' read -ra array <<< "$array_string"
echo "第一个参数: $first_arg"
echo "数组元素:"
for element in "${array[@]}"; do
echo " $element"
done
}
# 准备数组
my_array=("apple" "banana" "cherry")
# 将数组转换为字符串(使用逗号分隔)
array_string=$(IFS=','; echo "${my_array[*]}")
# 调用函数
my_function "hello" "$array_string"方案三:使用 Bash4.3+ 的新特性:名称引用
# 定义函数
my_function() {
local first_arg="$1"
local -n array_ref="$2" # 使用 nameref
echo "第一个参数: $first_arg"
echo "数组元素:"
for element in "${array_ref[@]}"; do
echo " $element"
done
}
# 准备数组
my_array=("apple" "banana" "cherry")
# 调用函数,传递数组名称
my_function "hello" my_array[!info]
local -n是 Bash 4.3 及以上版本引入的 nameref(名称引用) 功能,它允许你创建一个对另一个变量的引用。
通过 ${#数组[@]} 语法获取数组元素的个数来判断:
arr1=()
if [ ${#arr1[@]} -eq 0 ];then
echo "数组为空"
else
echo "数组不为空"
fi
通过 ${数组[@]} 语法获取数组所有元素,如果返回一个空值,则表明此数组为空:
arr1=()
isEmpty=true
for element in "${arr1[@]}"; do
isEmpty=false
break
done
if $isEmpty; then
echo "Array is empty"
fi[!info]
最「笨」的方式就是遍历数组,设个结果变量,判断每一个元素。
原生 Shell 不支持数学运算,可以通过 awk、expr 命令来实现。
num1=`expr 1+2`
echo num1
当然还有更便捷的方式,使用 (()) 来进行整数运算。
语法:${parameter//pattern/string}
用 string 来替换 parameter 变量中所有匹配的 pattern
s1="hello,shell,split,test"
array=(${s1//,/ })[!info]
s1字符串原有使用,来分隔,而经过${s1//,/ }后,就是将,替换成空格,利用 数组 构建时默认按空格分隔的规则,这些子串就被替换成使用空格来分隔,那最后构建成数组时,子串自动变成数组的各个元素。[!important]
注意最后的是
/斜杠后是有一个空格的,如果少了,就相当于将原有的分隔符,给「移除」了,那些原来用,分隔的子串,就会合并成一起了!-- 当然如果有这种将子串合并成一个字符串的需求,可以使用这种方式来对字符串进行合并。
# # 是从左向右,一个#是取第一个,##是取最后一个
# % 是从右向左 一个%是取第一个,%%是取最后一个
##
s1="https://github.com/rexdf/ChineseLocalization.git"
# 从左向右取第一个.后的字符串
# 即取到的是 com/rexdf/ChineseLocalization.git
echo "${s1#*.}"
# 从左向右取最后一个.后的字符串
# 即取到的是 git
echo "${s1##*.}"
# 从右向左取.后的第一个字符串
# 即取到的是 https://github.com/rexdf/ChineseLocalization
echo "${s1%.*}"
# 从右向左取最后一个.后的字符串
# 取到的是 http://github
echo "${s1%%.*}"
Tip
快速记忆是从左还是从右,可以这么记:键盘上 # 在左,而 % 在右边,所以 # 是从左向右,% 是从右向左。
* 的部分是「忽略」的部分。# 都是忽略左边,取右边;% 是忽略右边,取左边,所以 * 号决定使用 # 还是 %。
# core_address是 denolehov/obsidian-git 这个样子。获取 / 左右两段字符串
# 取前段 使用从右向左取,取 / 最后一段
local account=${core_address%%/*}
# 取后牌戏 使用从左向右取,同样取 / 最后一段
local p_name=${core_address##*/}
Tip
从左往右,* 与 # 在一起,而且 * 总在左边
从右往左,* 与 # 总被分隔符分离,* 总在右边。
字符串是可以遍历的。
str2="hello,world"
for s_temp in $str2; do
echo $s_temp
done
[!info]
示例 1 中最终输出就是
hello,world,就是把字符串中每一个字符「遍历」一遍。
str1="silas tom jack lucy mary"
for s_tem in $str1; do
echo $s_tem
done
[!info]
示例 2 中的字符串,是带空格的;所以遍历的结果是按空格分割,将分割后每段子串依次输出。
这种以空格为分隔符的字符串,也可以这样遍历:
for s_tem in ${str1[@]}; do echo $s_tem done其实这是一种 数组样式 的遍历。
字符串转 数组
默认情况,是按空格分隔,可以通过修改 IFS 的值来改变分隔符。
IFS=$'\n' # 设置内部字段分隔符为换行符
array=($string)
unset IFS # 恢复IFS为默认值if [ command ];then
符合该条件执行的语句
elif [ command ];then
符合该条件执行的语句
else
符合该条件执行的语句
fiwhile 与 for 读文件是有区别的:
while是逐行读取,读完一行跳转到下一行for是按字符串方式读取,遇到空格后,再读取的数据就会换行显示
while 相对于 for 的读取能更好的还原数据原始性。
for 变量名 in 循环列表
do
命令集
done
[!info]
这种
for循环语句语法中,for关键字后面会有一个「变量名」,变量名依次获取in关键字后面的变量取值列表内容(以空格分隔),每次仅取一个,然后进入循环(do和done之间的部分)执行循环内的所有指令,当执行到done时结束本次循环。之后,「变量名」再继续获取变量列表里的下一个变量值,继续执行循环内的所有指令,当执行到
done时结束返回,以此类推,直到取完变量列表里的最后一个值并进入循环执行到done结束为止。
for line in `cat xxx.txt`
do
echo $line
done写法 1:
cat xxx.txt | while read line
do
echo $line
done写法 2:
while read line$*用法
do
echo $line
done < xxx.txt这示例使用到了 jq 这个 json 小工具,对 json 文件进行解析,并将解析后的结果数据输出存放到一个临时文件中。
然后对这个临时文件进行读取,在读取时,还对读到的数据进行一定需求的过滤。
function get_dl_url(){
# json文件
local json_path=$1
# 使用 jq 获取各文件下载地址并输出到临时文件中
jq -r '.assets[] | .browser_download_url' $json_path > temp.txt
# 过滤掉 main.js manifest.json styles.css 三个文件之外所有文件
for line in `cat temp.txt`
do
# 从文件地址中获取文件名
local f_name=${line##*/}
# 过滤文件
if [[ $f_name == "main.js" ]] || [[ $f_name == "manifest.json" ]] || [[ $f_name == "styles.css" ]];then
echo $line
fi
done
}函数定义语法:
function 函数名(){
# 函数体
}[!info] 语法解释
跟 Javascript 等语言很像。
Shell 脚本内,传递参数格式为 $n,1为执行脚本的第一个参数,2为执行脚本的第二个参数,以此类推。
$#:传递到脚本的参数个数。$*:以一个单字符串显示所有向脚本传递的参数。
Tip
如果使用引号 " 括起来,是以 "$1 $2 ... $n" 形式输出所有参数。
$@:与$*相同,但是使用时加引号,并在引号中返回每一个参数。
Tip
如果使用引号 " 括起来,是以 "$1" "$2" ... "$n" 形式输出所有参数。
$* 与 $@ 跟 数组元素 中的 * 和 @ 类似,$* 是把参数当成一个「整体」处理,而 $@ 是单个参数的组合。
$$:脚本运行的当前进程 ID 号。$!:后台运行的最后一个进程 ID 号。$?:显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
判断是否一个参数都没传:
if [[ $# -eq 0 ]]; then
fi遍历参数:
for temp in "$@"; do
doneShell 返回值可以有两种方式进行返回:
- 使用
echo进行返回。使用这种方式返回的返回值都是字符串,获取方式可以是使用变量接收$(xxx)函数执行结果,也可以通过$?方式获取。 - 使用
return进行返回,这种方式只能返回整型。这种方式只能通过$?方式获取。
# 检测目录是否存在
# 返回值:0为存在 其余都是有问题的
function validate_dir() {
local dir_path=$1
# 检测路径是否为空
if [ -z $dir_path ]; then
echo -e "\e[93m Vault路径不能为空!\n \e[0m"
return 1
fi
# 目录存在
if [ ! -d "$dir_path" ]; then
echo -e "\e[93m $dir_path \e[96mVault路径不存在!\n \e[0m"
return 1
else
# return 0
echo 0
fi
}
# 检测 Vault 路径
r_1=$(validate_dir $1)
echo $r_1
# echo $?
Tip
示例中,因为需要判断后就结束函数,所以 return 是必须的,同时还要显示「错误」信息。
这样写,即满足业务需求,而且还能兼容 r_1=$(validate_dir $1) 及 echo $? 这两种获取返回值的方式。
其实函数所有返回值无论之前是什么类型,最后都是以 字符串 形式返回。
如果是返回 数组,实际返回一个带有空格的字符串。
虽然执行此函数时,获取到的这个返回值是可以遍历的,但它不能如正常数组一样,通过索引取某个元素。
要想「正常」使用,得转换成数组。
function test1() {
local arr1=("cat" "dog" "duck" "cock" "fish" "goose")
# 返回数组
echo ${arr1[@]}
}
# 执行函数test1 并获取返回值
an_arr=$(test1)
# for a_temp in ${an_arr[@]}; do
# echo $a_temp
# done
# for a_temp in $an_arr; do
# echo $a_temp
# done
# 转换成数组
r_arr=($an_arr)
echo ${r_arr[@]}在 Shell 中,三种方式解析命令行参数:
getopts,是 Bash 的内置命令。
但 getopts 只能处理短选项,如 -n 这种。
getopt 不是标准的 unix 命令,但大多数 Linux 发行版都自带了。
在非 Bash 的 sh 中,没有 getopts,所以可以使用 getopt 替代。
getopt 相较于 getopts 有个优势,就是它可以处理短选项,也可以处理长选项,如 --prefix=/home 这种。
getopt 老版本不太好用,后来的版本解决了老版本的问题,一般称为「增强版」。
,通过 -T 选项,即 getopt -T,就能检测当前发行版是否是「增强版」了。返回值为 4,就证明当前系统装的是增强版。
# silascript @ (base) in ~ [3:21:16]
$ getopt -T
# silascript @ (base) in ~ [3:21:18] C:4
$ echo $?
4read 命令是 Shell 非常重要而常用的命令。
read 有时需要与一个环境变量配置使用,如 IFS 分隔符:
inotifywait -mrq --timefmt "%d/%m/%y#%H:%M" --format '%T#%w#%f#%e' -e create,delete,modify,attrib "$config_dir" | while IFS=\# read date time dir file event; do这是一个段使用 inotify-tools 监控某个目录的代码,其实 while IFS=\# read date time dir file event 这是将监控信息读取到各个变量中,默认情况,是使用空格来分隔各个信息的,如时间 %T、%w 是事件触发的目录、%f 事件触发的文件及 %e 事件本身,但由于某些情况,监控的目录中有些目录的目录名或文件的文件名包含了空格,那这就给获取 %w 的值带来麻烦(指的就是 Sublime 的配置目录,其中有个 Installed Packages 目录及文件 Package Control.sublime-package,都存在空格),所以不得已,必须另外指定分隔符。例子中就使用 # 当分隔符,使用 IFS 来指定分隔符。
shellcheck 是一个 shell 的语法检查工具。
它是用 haskell 写的,所以装它时得把 haskell 一并给装了。
pacman -S shellcheckshfmt 是一款 shell 脚本格式化工具。
这工具可以与多款 文本编辑器 的 shell 格式化插件配合使用。
[!info]
shfmt -l -w script.sh-i:缩进设置。默认是 0,表示制表符缩进。大于 0 空格缩进,数字是就是空格数。
bn: && 及 | 另起一行
ci:switch case 缩进
sr:重定向符,>、>>、<< 这些,后面添加空格
fn:函数大括号,起始那个括号另起一行
kp:对齐
Google 风格:Style guides for Google-originated open-source projects
这是一个可以监控目录变化的小工具。
r:即recursive,递归查询目录。m:即monitor,始终保持监听,如果没有这个参数,inotifywait在接收一次事件之后就会退出。q:即quiet,就是只打印事件,最小化输出。e:即event,我们要监听的事件类型,多个事件用,分隔。
| 事件 | 解释 |
|---|---|
| access | 文件或者目录被读 |
| modify | 文件或目录被写入 |
| attrib | 文件或者目录属性被更改 |
| close_write | 文件或目录关闭,在写模式下打开后 |
| close_nowrite | 文件或目录关闭,在只读模式打开后 |
| close | 文件或目录关闭,而不管是读/写模式 |
| open | 文件或目录被打开 |
| moved_to | 文件或者目录移动到监视目录 |
| moved_from | 文件或者目录移出监视目录 |
| move | 文件或目录移出或者移入目录 |
| create | 文件或目录被创建在监视目录 |
| delete | 文件或者目录被删除在监视目录 |
| delete_self | 文件或目录移除,之后不再监听此文件或目录 |
| unmount | 文件系统取消挂载,之后不再监听此文件系统 |
--format :参数也会用到,是控制输出格式的。
%w: 表示发生事件的目录%f: 表示发生事件的文件%e: 表示发生的事件%Xe: 事件以“X”分隔%T: 使用由 timefmt 定义的时间格式
--timefmt:时间格式
示例及说明:
file_dir=$1
inotifywait -mrq --timefmt "%d/%m/%y %H:%M" --format "%T %w%f %e" -e create,delete,modify $file_dir | while read date time dirfile event[!info]
while read后四个变量date、time、dirfile和event,这是自定义的变量,用来获取 inotifywait 输出的信息。能获取几个 inotifywait 输入信息,主要看
--format这个参数。这个参数是 inotifywait 的输出格式。其中的参数值以空格分隔。如例子中%T %w%f %e,能获取三块信息%T,即时间信息,%w%f目录或文件路径信息,%e事件信息。而%T能获取几个时间信息,又是由--timefmt这个参数决定的。如例子中%d/%m/%y %H:%M,使用了一个空格分隔,所以实际能获取到的时间信息是两块,所以接收这个信息的变量个数应该非常注意,如果少了或多了,那就获取到错误部分的信息了。另外,
%w和%f,这两个,虽然一个为目录,一个为文件,但最好不要使用空格隔开,妄想使用这种方式分别获取目录和文件,最终效果是,如果触发事件的目标是一个目录,那结果是路径最后的子目录会被当成文件来获取(因为没有真的文件存在嘛)。-- 其实对于 Linux 而言,所有东西都是文件,目录与文件区分,只是人们为了方便而区分的。在 Linux 的目录树中,常看到用d来标识其中那些为目录的「文件」,而 inotifywait 触发事件中,那些目录触发的事件问题带着ISDIR标识,这与 Linux 的d标识的设计逻辑是一致的。
#json
shell 下有多款 json 小工具:
jq或jshon:shell 下的 JSON 解析器。JSON.sh、jsonv.sh:shell 脚本,能在 bash、zsh 等中解析 JSON。JSON.awk:JSON 解析器 awk 脚本。json.tool:python 模块。undercore-cli:基于 NodeJS 或 JS 的 json 工具。
#shell #tools #json #jq
jq 是一个 Shell 下操作 json 的小工具。
yay -S jqjq [options] <jq filter> [file...]
jq [options] --args <jq filter> [strings...]
jq [options] --jsonargs <jq filter> [JSON_TEXTS...]-c:紧凑而不是漂亮的输出-n:使用null作为单个输入值-e:根据输出设置退出状态代码-s:将所有输入读取(吸取)到数组中;应用过滤器;-r:输出原始字符串,而不是 JSON 文本-R:读取原始字符串,而不是 JSON 文本-C:为 JSON 着色-M:单色(不要为 JSON 着色)-S:在输出上排序对象的键--tab:使用制表符进行缩进;--arg a v:将变量$a设置为v--argjson a v: 将变量$a设置为 JSONv--slurpfile a f:将变量$a设置为从f读取的 JSON 文本数组--rawfile a f:将变量$a设置为包含f内容的字符串--args:其余参数是字符串参数,而不是文件--jsonargs:其余的参数是 JSON 参数,而不是文件--:终止参数处理
jq 支持一些内置函数,如 length, keys, values, tostring 等,用于操作和处理 JSON 数据。
del:直接删除目标字段,生成新对象。
map(f):对数组中的每个元素应用过滤器fsort:对数组中的元素进行排序sort_by(f):根据过滤器f的结果对数组排序min,max:找出数组最小值和最大值。reverse:反转数组元素的顺序
keys:函数是获取对象所有的键,并以数组形式返回values:函数是获取对象的值。map_values(f):对对象中每个值应用过滤器fhas(key):判断对象是否有某个键
contains(x):判断输入是否完全包含参数xtostring:将输入转换成字符串
# 从assets 数组中获取browser_download_url元素的值
# 过滤除了main.js manifest.json styles.css三个文件外所有文件
jq -r '.assets[] | .browser_download_url | select ( contains("main.js") or contains("manifest.json") or contains("styles.css") )' $json_path
[!info]
contains()方法是用来判断是否包含某字符串,包含返回true,否则返回falseselect()选择过滤数据
local tagstr="$2"
curl http://hub-mirror.c.163.com/v2/library/${image}/tags/list | jq --arg tstr $tagstr -r '.tags[]| select(contains($tstr))'[!info]
jq --arg是定义变量的选项jq --arg tstr $tagstr:tstr为形参变量,是 jq 内部使用;而$tagstr是实参,外部传进来的。要使用形参时,使用$打头,跟普通 shell 变量使用一致。
传多个参数:
dl_url=$(curl $channel_json_v3 | jq -r --arg pkg_name $package_name --arg pkg_version "$package_version" '.packages_cache.[].[]| select(.name==$pkg_name)|.releases[]| select(.version==$pkg_version).url')[!info]
pkg_name和pkg_version这两个是形参,用于 jq 内部引用的。
$package_name和$package_version是实参,是 jq 外部实际传进来的值。注意,
select(.name==$pkg_name)或select(.version==$pkg_version),引用「形参」时,不要加双引号,而且==不要加空格。「实参」
"$package_version"这个可以加双引号,防止传进来的字符串带有空格,被「自动切割」。
{
"vaults": {
"88a790a7d8b3e712": {
"path": "/home/silascript/MyNotes/ITNotes",
"ts": 1763336737061,
"open": true
},
"50006d35f784463b": {
"path": "/home/silascript/MyNotes/WritingNotes",
"ts": 1763353935904
},
"38ba7ce6d75f3dc4": {
"path": "/home/silascript/MyNotes/WritingExericse",
"ts": 1763304814607
},
"8e5254dd564849f2": {
"path": "/home/silascript/MyNotes/LHP_Note",
"ts": 1763324507963
}
},
"frame": "custom",
"disableGpu": true,
"updateDisabled": true
}如果想要根据 vault 的目录路径找到相应的 vault 的 ID,可以使用以下代码:
cat .config/obsidian/obsidian.json | jq -r '.vaults | map_values(select(.path=="/home/silascript/MyNotes/WritingExericse")) | keys'yq 是类似于 jq 的小工具,不过它用来解析 YAML 的,是一个轻量级的便携式命令行 YAML 处理器。它所以可以通过 pip 来安装。
因为 yq 是有入口程序的,所以也可以使用 pipx 来安装:
pipx install yq当然 yq 通过系统的包管理器安装:
(ArchLinux_Note 上包管理器中的 yq 是 python 版本,而 go 版的是叫 go-yq)
pacman -S go-yq- awesome-shell
- awesome-shell_ZH-CN
- Awesome Shell:命令行框架、工具包、指南清单(中译版) - Shell - 软件编程 - 深度开源
- shell echo 显示颜色 - 知乎
- 实现shell脚本中的转圈、进度条等一些效果 · 艾莉亚的猫