linux操作系统包含内核kernel
,人机交互程序与应用,其中shell就属于人机交互程序。
当我们在linux终端输入一个命令时,shell
会把他先转换为内核可以理解的命令,有内核执行并将执行结果返回到终端。shell在这里扮演的角色就是翻译。
同时shell也是一种编程语言。shell
有很多版本,目前linux默认装载的是Bash Shell
。
查看当前系统应用的shell
wangzhou@wangzhou-Latitude-5410:~$ echo $SHELL
/bin/bash
临时切换shell
wangzhou@wangzhou-Latitude-5410:~$ tcsh
wangzhou-Latitude-5410:~>
查看当前系统支持哪些版本shell。
wangzhou@wangzhou-Latitude-5410:~$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash
/bin/tcsh
/usr/bin/tcsh
前面提到shell
相当于一个翻译官,那么shell
是怎么找到要执行的命令(执行命令其实是执行脚本程序)的应用的位置呢?答案是通过环境变量。使用如下命令查看当前用户环境变量(不同用户有不同的环境变量)。
wangzhou@wangzhou-Latitude-5410:~$ echo $PATH
/home/wangzhou/Applications/node-v16.4.1-linux-x64/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/sbin/platform-tools/
增加环境变量。
法1:export
命令。这种方法是临时修改,只对当前窗口有效,重新打开一个新窗口就失效了。注意一定要加$PATH
,这表示追加环境变量,否则你的其它环境变量就没有了。
export PATH=$PATH:/HOME/ct
法2:可以通过修改文件来实现,这种方式是非临时修改。根目录下使用ls -a
可以看到.bashrc
(个人环境变量),.bash_profile
(个人用户环境变量),这两个文件都可以实现相同的功能,只不过前者是每打开一个新的窗口都会被加载一次,而后者在切换用户时加载一次。在/etc/profile
与/etc/bashrc
两个文件中修改环境变量则是系统级别,所有用户都会生效。
shell命令有19种,只要是linux的命令都可以被称shell命令。如此多的命令我们肯定记不住,我们着重讲解文档命令,这样不懂的命令使用文档命令就可以看到命令的详细介绍。
我们最常用的文档命令就是man
.下面应用他查下ls
的用法。
man ls
效果如下。
符号|
表示管道。
wangzhou@wangzhou-Latitude-5410:~$ ls -a
. anriodagent .bashrc Documents .gradle .local .mysql_history .npmrc platform-tools runoob .ssh_bak .viminfo 图片
.. Applications .cache Downloads IdeaProject logs .nbprofiler .oracle_jre_usage .profile snap .sudo_as_admin_successful .visualvm 桌面
adb .arthas .config .gitconfig IdeaProjects .m2 nodedev .pam_environment Public soft Templates .vscode
a.jpg .bash_history Desktop .gnupg .java .mozilla .node_repl_history Pictures .python_history springdemo .thunderbird .wget-hsts
.android .bash_logout dev .gphoto learngit Music .npm .pki .rpmdb .ssh Videos 下载
wangzhou@wangzhou-Latitude-5410:~$ ls -a | grep .bash
.bash_history
.bash_logout
.bashrc
上面ls -a | grep .bash
会先执行ls -a
,然后把执行结果传递给管道,管道再传给grep .bash
执行过滤。可以多次使用管道。
(1) <
与>
<
,>
表示输入、输出符号。它主要有两个作用:重定向和输入输出。先理解下重定向。
linux所有的文件、目录都有一个文件描述符作为唯一标识。我们可以使用文件描述符来访问linux操作系统的所有文件、目录。后面我们也会体会到这种机制的好处。
首先将之前ls -a
查询到的内容保存在文件1.txt中,然后使用exec 3< 1.txt
将文件1.txt标识为3,类型是标准输入。现在执行grep .bash <&3
,效果如下。
可以看到,标准输入指的是将3所指代的内容输入到目标文件/命令,而标准输出则是从目标文件/命令输出到标识3所指代文件,输入输出是相对于目标而言的。
注意:0
(STDIN),1
(STDOUT),2
(STDERR)是系统自带的标识,请不要使用。
其实上面重定向已经包含了输入输出的作用了,下面单独使用他的输入、输出功能,将ls -a的内容从1.txt输出。
wangzhou@wangzhou-Latitude-5410:~$ ls -a > 1.txt
wangzhou@wangzhou-Latitude-5410:~$ cat 1.txt
(2)>>
与<<
>>
与<<
和<
,>
作用类似,不过是追加模式,使用方式如下。
wangzhou@wangzhou-Latitude-5410:~$ cat << END
> 123
> 234
> END
123
234
(3) read
使用read
可以通过键盘按行定义变量。
wangzhou@wangzhou-Latitude-5410:~$ read a
/Desktop
wangzhou@wangzhou-Latitude-5410:~$ ls -a `echo $a`
. ..
为实现某个任务,将多个shell命令组合成可执行文件就是shell脚本。shell脚本用文档编辑器就可以编写,给予可执行权限就可以执行。
新建a.sh
。
#!/bin/bash
# This is a test scrpit.
# time:2021-11-19
# autor:z
# motification time:
# motifycation author:
echo "hello,world!"
exit 0
#
表示注解,其作用是解释脚本功能,记录脚本的创建、修改时间、作者等。
#!/bin/bash
指定了该shell脚本的解析器,建议文件后缀与解析器的指定匹配,比如.sh
就对应bash
解析器。
指定执行权限。+x
参数表示给所有用户赋予执行权限,u+x
则表示给当前用户赋予权限。
chmod +x a.sh
执行结果如下。
wangzhou@wangzhou-Latitude-5410:~$ ./a.sh
hello,world!
另外,将a.sh的路径加到环境变量中,就可以像其它普通的shell命令一样执行他了。
使用set
命令可以查看已经被linux系统占用的变量(相当于java的保留字),由于个数太多,我们筛选了少部分看即可。
set | grep -e HOME -e BASH
在b.sh中随便打个系统变量看看。
echo "hello,world!"
echo "\$0":$0
exit 0
执行结果如下。
wangzhou@wangzhou-Latitude-5410:~$ ./a.sh
hello,world!
$0:./a.sh
其它常用的shell系统变量参考下表。
自己定义变量很简单,有字母、数字、下划线组成。局部变量和全局变量定义示范如下。
#!/bin/bash
var=10
function getvar(){
local var1=1
echo "var1:$var1"
echo "var:$var"
}
getvar
执行结果如下。
wangzhou@wangzhou-Latitude-5410:~$ ./b.sh
var1:1
var:10
参考下列代码,值的注意的是,shell中数组长度可变。
wangzhou@wangzhou-Latitude-5410:~$ ARRAY=(1 2 4)
wangzhou@wangzhou-Latitude-5410:~$ echo $ARRAY
1
wangzhou@wangzhou-Latitude-5410:~$ echo ${ARRAY[0]}
1
wangzhou@wangzhou-Latitude-5410:~$ echo ${ARRAY[@]}
1 2 4
wangzhou@wangzhou-Latitude-5410:~$ echo ${ARRAY[@]:1}
2 4
wangzhou@wangzhou-Latitude-5410:~$ ARRAY[3]=7
wangzhou@wangzhou-Latitude-5410:~$ echo ${ARRAY[@]:1}
2 4 7
如下表。
我们使用let
来进行运算。下面示例如下。
wangzhou@wangzhou-Latitude-5410:~$ let var=11%2
wangzhou@wangzhou-Latitude-5410:~$ echo $var
1
八进制以0
开头,0x
十六进制。
wangzhou@wangzhou-Latitude-5410:~$ let var=011
wangzhou@wangzhou-Latitude-5410:~$ echo $var
9
可以自定义进制,比如二进制。
wangzhou@wangzhou-Latitude-5410:~$ let var=2#111
wangzhou@wangzhou-Latitude-5410:~$ echo $var
7
下面代码在实际shell编程中常用于脚本连接:下一个shell脚本常常依赖于上一个脚本,我们先依次检查每一个脚本,避免前面脚本的错误影响后面脚本的结果。
vim d.sh
#!/bin/bash
ls $1
if[$? -eq 0];then
echo "The last exit code is 0"
else
echo "The last exit code is not 0"
exit 1
fi
执行 ./d.sh a.sh
则输出"The last exit code is 0",执行./d.sh e.sh
则输出“The last exit code not 0",这是因为e.sh都还没有创建呢。
条件判断的格式还有扩展写法。
if [ command ];then
符合该条件执行的语句
elif [ command ];then
符合该条件执行的语句
else
符合该条件执行的语句
fi
常用的一些类型的判断有默认方法,这些方法常常结合上面的if语句使用,十分方便。
[ -a FILE ] 如果 FILE 存在则为真。
[ -d FILE ] 如果 FILE 存在且是一个目录则返回为真。
[ -e FILE ] 如果 指定的文件或目录存在时返回为真。
[ -f FILE ] 如果 FILE 存在且是一个普通文件则返回为真。
[ -r FILE ] 如果 FILE 存在且是可读的则返回为真。
[ -w FILE ] 如果 FILE 存在且是可写的则返回为真。(一个目录为了它的内容被访问必然是可执行的)
[ -x FILE ] 如果 FILE 存在且是可执行的则返回为真。
[ -z STRING ] 如果STRING的长度为零则返回为真,即空是真
[ -n STRING ] 如果STRING的长度非零则返回为真,即非空是真
[ STRING1 ] 如果字符串不为空则返回为真,与-n类似
[ STRING1 == STRING2 ] 如果两个字符串相同则返回为真
[ STRING1 != STRING2 ] 如果字符串不相同则返回为真
[ STRING1 < STRING2 ] 如果 “STRING1”字典排序在“STRING2”前面则返回为真。
[ STRING1 > STRING2 ] 如果 “STRING1”字典排序在“STRING2”后面则返回为真。
[ INT1 -eq INT2 ] INT1和INT2两数相等返回为真 ,=
[ INT1 -ne INT2 ] INT1和INT2两数不等返回为真 ,<>
[ INT1 -gt INT2 ] INT1大于INT2返回为真 ,>
[ INT1 -ge INT2 ] INT1大于等于INT2返回为真,>=
[ INT1 -lt INT2 ] INT1小于INT2返回为真 ,<
[ INT1 -le INT2 ] INT1小于等于INT2返回为真,<=
[ ! EXPR ] 逻辑非,如果 EXPR 是false则返回为真。
[ EXPR1 -a EXPR2 ] 逻辑与,如果 EXPR1 and EXPR2 全真则返回为真。
[ EXPR1 -o EXPR2 ] 逻辑或,如果 EXPR1 或者 EXPR2 为真则返回为真。
[ ] || [ ] 用OR来合并两个条件
[ ] && [ ] 用AND来合并两个条件
for循环。
for 变量 in 串行
do
执行命令
done
while循环:当条件满足时执行。
while 条件测试
do
执行命令
done
util循环:直到满足条件停止。
until 条件测试
do
执行命令
done
使用break
关键字可以跳出循环,而且还可以跳出嵌套循环,下面给出了示例代码,特别注意空格与示例一致,不加空格的后果您感兴趣可以尝试。
$ cat test1.sh
#!/bin/bash
i=1
while true
do
if [ $i -gt 3 ] #没有空格则报错
then
break
fi
echo "i="$i
sleep 1
i=`expr $i + 1` # 有空格是数字运算,没有空格是字符串拼接
done
执行结果如下。
24724@LAPTOP-OCSC7S98 MINGW64 ~
$ ./test1.sh
i=1
i=2
i=3
下面示例break 2
跳出双层循环。
#!/bin/bash
i=1
j=1
while true
do
while true
do
if [ $i -gt 3 ]
then
break 2
fi
echo "i="$i
echo "j="$j
sleep 1
i=`expr $i + 1`
done
j=`expr $i + $j`
echo "j="$j
done
执行结果如下,说明在跳出内存循环时也跳出了第二层外层循环。
24724@LAPTOP-OCSC7S98 MINGW64 ~
$ ./test1.sh
i=1
j=1
i=2
j=1
i=3
j=1
关键字continue
可以跳出当层循环,这里不再赘述。
最简单的交互。使用read
实现用户交互,让用户输入密码。
echo -e "请输入密码: \c"
pwdstr=''
while : ;
do
read -n 1 -s -p "" pw
if [ $pw ]; then
pwdstr=${pwdstr}$pw
echo -e "*\c"
else
echo
break
fi
done
echo "password is :$pwdstr"
执行结果如下。
$ ./test2.sh
password is :echo -e 请输入密码: \c
******
password is :123455
第二种方式是通过菜单选择来实现交互。
function menu ()
#!/bin/bash
select i in a b c d
do
case $i in
a)
ls
;;
b)
pwd
;;
c)
w
;;
d)
uname -a
;;
*)
echo "Please input 1-4"
;;
esac
done
效果如下。
$ ./test2.sh
1) a
2) b
3) c
4) d
#? a
Please input 1-4
#? 1
「开始」菜单 Contacts git.ignore 'My Documents' NTUSER.DAT{53b39e88-18c4-11ea-a811-000d3aa4692b}.TM.blf Pictures SendTo
'3D Objects' Cookies IntelGraphicsProfiles NetHood NTUSER.DAT{53b39e88-18c4-11ea-a811-000d3aa4692b}.TMContainer00000000000000000001.regtrans-ms PrintHood Templates
AppData Desktop Links NTUSER.DAT NTUSER.DAT{53b39e88-18c4-11ea-a811-000d3aa4692b}.TMContainer00000000000000000002.regtrans-ms Recent test1.sh
'Application Data' Documents 'Local Settings' ntuser.dat.LOG1 ntuser.ini 'Saved Games' test2.sh
BullseyeCoverageError.txt Downloads Music ntuser.dat.LOG2 OneDrive Searches Videos
#? 2
/c/Users/24724
grep
过滤,sed
批量行处理,awk
批量列处理。号称文本处理三剑客,特别常用。这里仅仅抛砖引玉,读者需要使用他们进行文本处理时可以参考博客:https://www.cnblogs.com/along21/p/10366886.html.