目录
一、基本语法
1. 基础知识
2. if判断
3. 循环遍历
介绍一个{ n1..n2 }, seq -s'分隔符' n1 n2.
4. 函数脚本传参
结果会是如何?
5. 字符串常规操作
6. 字符串处理:awk、sed、tr。留着下次,太多了
一、基本语法
1. 基础知识
- shell是个啥?
外壳程序,命令界面。系统提供给用户的命令界面。
用户通过shell程序输入命令,shell程序通知系统内核,内核操作硬件资源。shell解释器本质上也是一个程序,C语言编写的编译好的可执行程序,上面运行的很多命令也是,本质上都是通过调用各种系统调用来操作内核的。前辈们写好的了的。shell要解释这些命令,为啥,因为可能命令很复杂,通过管道实现命令套命令,一个命令的输出作为另一个命令的输入,会很复杂,所以需要解释。写个简易版的也不难,C语言中不也能创建管道,一个道理,通过管道将前一个可执行程序的标准输出重定向到后一个可执行程序的标准输入就行。
if (pid == 0) {
// 子进程1:执行第一个命令
close(pipefd[0]); // 关闭读取端
dup2(pipefd[1], STDOUT_FILENO); // 重定向标准输出到管道的写入端
close(pipefd[1]); // 关闭写入端副本
execlp("ls", "ls", NULL); // 执行第一个命令
perror("execlp");
exit(EXIT_FAILURE);
} else {
// 父进程
pid = fork();
if (pid == 0) {
// 子进程2:执行第二个命令
close(pipefd[1]); // 关闭写入端
dup2(pipefd[0], STDIN_FILENO); // 重定向标准输入到管道的读取端
close(pipefd[0]); // 关闭读取端副本
execlp("grep", "grep", "pattern", NULL); // 执行第二个命令
perror("execlp");
exit(EXIT_FAILURE);
} else {
// 父进程:关闭管道的所有端口
close(pipefd[0]);
close(pipefd[1]);
wait(NULL); // 等待所有子进程结束
wait(NULL);
}
}
- shell脚本又是一个啥?
一门脚本语言,由于支持各种强大的字符串/文本处理工具,比如sed、awk、tr这些个。并且可以将各种Linux命令打包放入一个脚本中,将这些命令组装起来,再加上结构化的编程语法控制。判断if、循环重复while、多分支选择case、函数打包function多条命令顺序执行。它就可以很方便的在Linux下结合系统命令开发一些常用的工具。自动化的做一些简单的文本处理,定时处理工作。用的多的命令也就
- cut/sort/uniq/wc/sed/awk/tr/grep这些个文本/字符串处理
- /netstat/free/du/df/ 这些个系统资源检测命令
- ps+grep+kill+killall服务搜索/杀死命令
- ls/cd/cp/mv/ln/mkdir/rm/ 这些个文件目录管理命令
- adduser/addgroup/chown/chgrp/chmod等修改文件所属组属主以及权限的命令
- curl/telnet/wget/rpm/yum/tar/rpm/export/source 这些远程网络服务请求,远程文件/包下载安装,导出环境变量、导出函数和可执行程序在当前环境下运行这些命令都很常见
- shell脚本语言是一门弱语言,不需要像C/C++/Java那样声明变量类型,默认都是字符串,也不需要考虑变量的范围、长度,担心会不会超出变量的表示范围。不至于,默认全都是当作字符串看待,数字运算也都比较简单
- source 和 点. 表示在当前shell环境下执行脚本和加载变量函数,而不是启动一个子shell/bash去做。如果是用/bin/bash解释器去执行脚本,相当于启动一个新的子进程,execv一个出来去执行脚本,
就如同下面这种,命令都在bash下面执行,我还可以启动sh解释器,再在bash下面execv(sh, args) 然后再在sh下面execv(pstree, args). 能理解吗,可惜source不能执行二进制文件,只能执行脚本。不然就能看见─sshd───sshd───sshd───bash───pstree,而没有sh了,相当于在当前进程下直接执行脚本或者导出脚本文件。
─sshd───sshd───sshd───bash───pstree
─sshd───sshd───sshd───bash───sh───pstree
twj@twj-ThinkPad-T420:~$ source pstree
bash: source: /usr/bin/pstree: cannot execute binary file
写个案例感受下source和.:
twj@twj-ThinkPad-T420:~$ cat functions
echo "nihao"
func1() {
echo "I am func1"
}
func2() {
echo "I am func2"
}
twj@twj-ThinkPad-T420:~$ source functions
nihao
twj@twj-ThinkPad-T420:~$ func1
I am func1
twj@twj-ThinkPad-T420:~$ func2
I am func2
如果只是执行functions呢?相当于启动一个子进程bash 去解释functions脚本。解释完退出回到父bash,父bash对刚刚的解释一无所知。
twj@twj-ThinkPad-T420:~$ /bin/bash functions
nihao
twj@twj-ThinkPad-T420:~$ func1
func1: command not found
twj@twj-ThinkPad-T420:~$ func2
func2: command not found
- alias 别名。* 通配0个或多个字符。 ? 通配一个字符
- shell中的转义字符 \ 单引号 ' '
对于 \ 转义字符,和'' 转义字符使用的情况不一致。
\往往用于转义特殊字符。不让其被特殊解释。''往往用于包裹字符串,让字符串中的特殊字符不被解释。
比如:使用sed情况下,{N} 包裹前面的匹配的字符的次数,花括号需要用/转义,否则会被特殊解释,具体解释成啥我也还不太明白,一点点积累吧。
\还可以转义它本身,比如说我们需要输出一个路径的时候.
| < >
这些符号本身都有着各自的特殊含义。管道、重定向,如果我们像表达它们最原本的含义,不被特殊解释,就需要使用 \
转义
twj@twj-ThinkPad-T420:~$ [ 3 \< 5 ]
twj@twj-ThinkPad-T420:~$ echo $?
0
twj@twj-ThinkPad-T420:~$ [ 3 < 5 ]
-bash: 5: No such file or directory
# 如果直接用|,bash会将其解析为管道符命令,
# 导致原本的或含义无法展现
twj@twj-ThinkPad-T420:~$ cat data.txt | grep "abc|hig"
twj@twj-ThinkPad-T420:~$ cat data.txt | grep "abc\|hig"
abc
hig
因为shell脚本中存在过多的特殊字符,所以一定不能轻视转义字符的使用,也许带上\,命令就可以正常工作。
- 命令执行字符 `command` $(command),两者都可以获取命令的执行结果
- 这两货个人感觉蛮重要的,可以提前获取一个命令的执行结果,再基于这个结果再提取,再做进一步的处理,接上管道符 | 就好了
twj@twj-ThinkPad-T420:~$ date -d"2023-07-04 12:00:00" +%s
1688443200
twj@twj-ThinkPad-T420:~$ timestamp=$(date -d"2023-07-04 12:00:00" +%s)
twj@twj-ThinkPad-T420:~$ date -d@$timestamp +%Y-%m
2023-07
twj@twj-ThinkPad-T420:~$ date -d@`date -d"2023-07-04 12:00:00" +%s` +%Y-%m
2023-07
- | 管道符,用于连接上一条命令输出和下一条命令的输入
- $? 判断上一条命令的执行结果 0 执行成功,非0 则执行失败。在脚本中非常常见。经常结合grep查找指令、或者[ ] 判断指令一同使用。可以获取上一条指令的执行结果,返回值。也就是那个exit status_code. exit 0 一般表示成功嘛
- 作业控制:
ctrl Z 暂停作业
bg 切入后台执行 等价于 可执行程序 参数 &
fg 编号 调回前台执行 之后再 ctrl C 终止
ctrl C 终止作业
jobs 可以查看暂停和后台作业
- 最常用的后台执行指令&
重定向:
- 1>file:将标准输出(stdout)重定向到文件 file。
- 2>file:将标准错误输出重定向到文件 file。
- &>file:将标准输出和标准错误输出都重定向到文件 file。
- 1>file 2>&1:将标准输出重定向到文件 file,并将标准错误输出重定向到标准输出(即两个输出都写入同一个文件)。
- 2>&1 将标准错误重定向到标准输出,1>/dev/null 把标准输出重定向到空设备文件
./command args 2>&1 1>/dev/null &
ping www.baidu.com 2>&1 1>/dev/null &
[1] 27613
ping www.baidu.com 1>/dev/null &
[2] 27624
jobs
[1]- Running ping www.baidu.com 2>&1 > /dev/null &
[2]+ Running ping www.baidu.com > /dev/null &
》提示:区分 1> 2> &> >&
1>,2>,&> 都是将输出重定向到一个文件,而>&则是将输出流重定向到一个输出流(即为重定向到该输出流的文件描述符)
代码案例如下:
twj@twj-ThinkPad-T420:~$ echo "张三 李四 王五" 1> /dev/null
twj@twj-ThinkPad-T420:~$ echo "张三 李四 王五" 2> /dev/null
张三 李四 王五
twj@twj-ThinkPad-T420:~$ echo "张三 李四 王五" &> /dev/null
# 如下的案例是将标准错误输出重定向到空文件,标准输出冲向到标准错误输出
# 先处理文件描述符重定向,然后才是文件重定向
# 所以相当于 &> /dev/null
twj@twj-ThinkPad-T420:~$ echo "张三 李四 王五" 2> /dev/null 1 >& 2
- $# 传入参数的数目
- $n, $0指的是脚本名称,$1-$9指的是传入参数。后续函数脚本参数传递时候有案例
- $*,$@指的是传入的所有参数,如果是字符串,$@会看作独立的参数而$*则会看作合并成一个串看待,这是它两的差异。后续介绍遍历时候有案例
- shell中空格往往用来分隔命令和参数,参数和参数。所以如果是参数中带有空格需要外面加上双引号""以避免参数被分开解析,案例如下
twj@twj-ThinkPad-T420:~$ date +%Y-%m-%d %H:%M:%S
date: extra operand ‘%H:%M:%S’ # 被当作多出的参数了,date命令无法解析
Try 'date --help' for more information.
twj@twj-ThinkPad-T420:~$ date +"%Y-%m-%d %H:%M:%S"
2024-08-04 18:52:40
时间获取和转换
- date命令,打印当前时间 date +"%Y-%m-%d %H:%M:%S" 用的是比较多的。
- 对应Y年份,m月份,d日期
- 还有一种特殊的用法:几个月前,几天前. 如果需要判断或者保持几个月以内的数据存储处理啥的,或许可以用到它。比如保存6个月以内的数据,其他的数据进行清理和删除掉。
[root@localhost bin]> date -d "6 months ago" +"%Y%m"
202401
[root@localhost bin]> date -d "6 days ago" +"%d"
25
[root@localhost bin]> date -d "10 days ago" +"%d"
21
- %s获取时间戳. -d@时间戳,时间戳转时间
[root@localhost bin]> date -d@`date +%s` +%Y%m
202407
- 数学运算
a=1
b=2
sum=$((a+b))
echo "$a+$b=$sum"
- 和用户的交互读取变量 -p 可以带上文本提示。相当于scanf()
read -p "please input a number: " x
read -p "please input another number: " y
sum=$((x+y))
echo "The sum of the two numbers is: ${sum}"
- 上述也可以用echo+不带选项的read代替
echo -n "提示信息: ",-n 不添加换行符输出。如果不带上-n选项,这个echo内容就会自动换行,变量的输入也就在下一行了。
twj@twj-ThinkPad-T420:~$ echo "hell"
hell
twj@twj-ThinkPad-T420:~$ echo -n "hell"
helltwj@twj-ThinkPad-T420:~$
echo -n "please input number: "
read x
echo "input number is $x"
2. if判断
- [ ] 基本的判断式
- [ ] 用于数字比较 -eq -gt -lt -le -ge
- [ ] 用于字符串比较 < > =
数字比较案例: 仅用于数字比较,有字符串如果不能转换会报错
[root@localhost bin]> [ 2 -eq 4 ]
[root@localhost bin]> echo $?
1
[root@localhost bin]> [ 2 -lt 4 ]
[root@localhost bin]> echo $?
0
[root@localhost bin]> [ 2 -gt 4 ]
[root@localhost bin]> echo $?
1
[root@localhost bin]> [ 4 -le 4 ]
[root@localhost bin]> echo $?
0
[root@localhost bin]> [ 4 -le sd ]
-bash: [: sd: integer expression expected
字符串比较判断案例:
= 比较字符串是否相等,!= 不等,-n :not zero, 也就是字符串是否不为空,-z: zero 字符串是否为空,我一般把这个zero理解为空。
twj@twj-ThinkPad-T420:~$ [ "abc" = "abc" ]
twj@twj-ThinkPad-T420:~$ echo $?
0
twj@twj-ThinkPad-T420:~$ [ "abc" != "abc" ]
twj@twj-ThinkPad-T420:~$ echo $?
1
twj@twj-ThinkPad-T420:~$ [ -n "abc" ]
twj@twj-ThinkPad-T420:~$ echo $?
0
twj@twj-ThinkPad-T420:~$ [ -z "abc" ]
twj@twj-ThinkPad-T420:~$ echo $?
1
twj@twj-ThinkPad-T420:~$ [ -z "" ]
twj@twj-ThinkPad-T420:~$ echo $?
0
多条件判断
-a: and 且
-o: or 或
twj@twj-ThinkPad-T420:~$ [ 2 -lt 3 -a 4 -lt 5 ]
twj@twj-ThinkPad-T420:~$ echo $?
0
twj@twj-ThinkPad-T420:~$ [ 2 -gt 3 -o 4 -gt 5 ]
twj@twj-ThinkPad-T420:~$ echo $?
1
twj@twj-ThinkPad-T420:~$ [ 2 -gt 3 -o 4 -lt 5 ]
twj@twj-ThinkPad-T420:~$ echo $?
0
文件常用判断,存在、类型、权限。这个就不写案例了,测测就懂。
-e: 文件是否存在
-f: 是否为普通文件
-d: 是否为目录文件
-r/-w/-x: 是否可读可写可执行
-s: 非空
扩展的[[]]判断,可以支持正则判断,下次跟着最下面那几个强大的武器一起介绍。
3. 循环遍历
-
介绍一个{ n1..n2 }, seq -s'分隔符' n1 n2
都是生成n1, n2的数字序列。seq还可以指定分隔符号。
for i in {1..3}; do echo $i; done
[tangyujie@VM-4-9-centos ~]$ for i in `seq 1 3`; do echo $i; done
1
2
3
[tangyujie@VM-4-9-centos ~]$ for i in `seq -s"\n" 1 3`; do echo $i; done
1\n2\n3
[tangyujie@VM-4-9-centos ~]$ for i in `seq -s"aaa" 1 3`; do echo $i; done
1aaa2aaa3
[tangyujie@VM-4-9-centos ~]$ for i in `seq -s"-" 1 3`; do echo $i; done
1-2-3
for i in `seq n1 n2`; do
done
for i in ${arr[@]}; do
done
while:; do
if [ cond ] break/continue/exit
done
4. 函数脚本传参
twj@twj-ThinkPad-T420:~/Scripts$ cat printArgs.sh
#!/bin/bash
echo "参数个数为$#"
echo "全部参数为$@"
echo "全部参数为$*"
echo "script_name:$0"
echo "参数遍历如下:-------------"
echo '方式1 $*'
for i in "$*"; do
echo "$i "
done
echo '方式2 $@'
for i in "$@"; do
echo $i
done
echo "参数遍历结束--------------"
结果会是如何?
对于$* 会把 a b c 合并成一个串输出。针对$@会单独当作一个一个串输出。看结果如下,为了验证,我刻意给$*的输出加了很多空格。但是明显没卵用,因为就输出了一次"a b c " 这一个串
参数个数为3
全部参数为a b c
全部参数为a b c
script_name:./printArgs.sh
参数遍历如下:-------------
方式1 $*
a b c
方式2 $@
a
b
c
参数遍历结束--------------
5. 字符串常规操作
切片操作:${str_var:begin_ind:length}
其他字符串的复杂操作,我没有去记住、太多了,我感觉不如直接结合下面的sed、awk、tr、正则再做处理。可以减轻记忆负担。可能非原生,加入太多的|管道效率会变低,但是脚本执行应该没那么考虑效率吧。我觉得咋个好做,咋个好用就咋个用。
[tangyujie@VM-4-9-centos ~]$ echo ${str:0:3}
abc
[tangyujie@VM-4-9-centos ~]$ echo ${str:3:3}
efg
[tangyujie@VM-4-9-centos ~]$ echo ${str:6:3}
hig
6. 列表操作
就介绍一个怎么添加元素,怎么遍历。具体怎么做,注意事项看看代码就应该可以懂得,对比测试一下就好了。
twj@twj-ThinkPad-T420:~/Scripts$ cat test_arr.sh
#!/bin/bash
arr=()
echo "让我来添加一个元素---------abc"
echo "arr+="abc" 可以不可以这样子做?"
arr+="abc"
arr+="efg"
arr+="hij"
for i in "${arr[@]}"; do
echo $i
done
echo "貌似这个结果并非我想要的-------"
arr=()
echo "让我来添加一个元素---------abc"
echo "arr+=("abc") 可以不可以这样子做?"
arr+=("abc")
arr+=("efg")
arr+=("hij")
for i in "${arr[@]}"; do
echo $i
done
echo "貌似这个结果就是我想要的-------"
twj@twj-ThinkPad-T420:~/Scripts$ ./test_arr.sh
让我来添加一个元素---------abc
arr+=abc 可以不可以这样子做?
abcefghij
貌似这个结果并非我想要的-------
让我来添加一个元素---------abc
arr+=(abc) 可以不可以这样子做?
abc
efg
hij
貌似这个结果就是我想要的-------
7. 字符串处理:awk、sed、tr。留着下次,太多了
后面如果有空,还会把常用命令介绍下。但是我感觉那个得靠自己积累,纯记忆性。