一、shell介绍

1.1 shell起源

  • 1964年,美国AT&T公司的贝尔实验室、麻省理工学院及美国通用电气公司共同参与开始研发一套可以安装在大型主机上的多用户、多任务的操作系统,该操作系统的名称为Multics。

  • 1970年,丹尼斯•里奇和汤普逊启动了另外一个新的多用户、多任务的操作系统的项目,他们把这个项目称之为UNICS。

  • 1973年,使用C语言重写编写了Unix。通过这次编写,使得Unix得以移植到其他的小型机上面。

  • 1979年,第一个重要的标准UNIX Shell在Unix的第7版中推出,并以作者史蒂夫•伯恩(StephenBourne)的名字命名,叫做Bourne Shell,简称为sh。

  • 20世纪70年代末,C Shell作为2BSD UNIX的一部分发布,简称csh。之后又出现了许多其他的Shell程序,主要包括Tenex C Shell(tcsh)、Korn Shell(ksh)以及GNU Bourne-Again shell(bash)。

img

查看当前系统支持的shell:

[root@server ~]# cat  /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash

查看当前系统默认shell

[root@server ~]# echo $SHELL
/bin/bash

1.2 shell是什么

1.2.1 shell是命令解释器

shell(壳)能识别用户输入的各种命令,并传递给操作系统。

image-20230129100929087

真正能够控制计算机硬件(CPU、内存、显示器等)的只有操作系统内核(Kernel),图形界面和命令行只是架设在用户和内核之间的一座桥梁,由于安全、复杂、繁琐等原因,用户不能直接接触内核(也没有必要),需要另外再开发一个程序,让用户直接使用这个程序;该程序的作用就是接收用户的操作(点击图标、输入命令),并进行简单的处理,然后再传递给内核,这样用户就能间接地使用操作系统内核。

用户界面和命令行就是这个另外开发的程序,就是这层“代理”。在Linux下,这个命令行程序叫做 Shell,Shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。

Shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序,它和 QQ、迅雷、Firefox 等其它软件没有什么区别。然而 Shell 也有着它的特殊性,就是开机立马启动,并呈现在用户面前;用户通过 Shell 来使用 Linux,不启动 Shell 的话,用户就没办法使用 Linux。

1.2.2 shell是脚本语言

如果有一系列经常需要使用的shell命令,可以将其存储在一个文件里,shell可以读取这个文件并顺序执行其中的命令,我们把这样的文件就叫shell脚本。

编程语言 C/C++、java、Go语言等,必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件在计算机中运行。而shell是一门解释性语言,shell脚本不需要编译即可执行,即直接运行源码。

编译型语言的优点是执行速度快、对硬件要求低、保密性好,适合开发操作系统、大型应用程序、数据库等。

脚本语言的优点是使用灵活、部署容易、跨平台性好,非常适合 Web 开发以及小工具的制作。

shell脚本的优势在于处理操作系统底层的业务,因为有大量的linux系统命令为它做支撑。2000多个命令都是shell脚本编程的有力支撑,特别是grep、awk、sed等。

1.3 脚本的书写规范与执行

1.3.1脚本的书写规范

  • 脚本文件的名称:建议脚本文件名以.sh结尾,见名知意

  • 第一行声明:#!/bin/bash

  • 注释:其余以#开头的行表示注释

    • 尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰。

    • 单行注释,可以放在代码行的尾部或代码行的上部。

    • 多行注释,用于注解复杂的功能说明,可以放在程序体中,也可以放在代码块的开始部分。

      多行注释写法:使用冒号“:”配合here document,语法如下:xxxx 可以是字符或数字,单引号可以不加,但以防出现莫名其妙的意外发生,比如发生字符扩展、命令替换。

      :<<'xxxx'
      comment1
      comment2
      comment3
      ……
      xxxx
      

    自动生成脚本开头的注释和说明:通过给root账号配置vim的“个性化默认设置”,使用root用户vim编辑创建.sh文件时会自动生成下面的关于脚本文件的注释说明。

    [root@server ~]# vim /root/.vimrc
    autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()"
    func SetTitle()
    if expand("%:e") == 'sh'
      call setline(1,"#!/bin/bash")
      call setline(2,"#########################")
      call setline(3,"#File name:".expand("%"))
      call setline(4,"#Version:v1.0")
      call setline(5,"#Email:admin@test.com")
      call setline(6,"#Created time:".strftime("%F %T"))
      call setline(7,"#Description:")
      call setline(8,"#########################")
      call setline(9,"")
    endif
    endfunc
    
  • 脚本中的语句:定义函数,定义变量并赋值,shell命令,流程控制语句等

    脚本执行后,打印内容到屏幕:

    • echo

      [root@server ~]# echo hello world
      hello world
      #-n不打印换行
      [root@server ~]# echo -n  hello world
      hello world[root@server ~]#
      #-e启用转义字符;格式:echo  -e  "\e[字体控制;字体颜色或背景色  字符串内容  \e[0m"
      #\e[表示控制开始,\e[0m表示控制结束
      #字体控制选项:1表示高亮,4表示下划线,5颜色闪烁
      #字颜色:30-37 , 背景色:40-47
      [root@server ~]# echo -e "\e[1;31m红色字\e[0m"
      红色字
      [root@server ~]# echo -e "\e[4;45;37m 紫底白字\e[0m"
       紫底白字
      
    • printf

      #需要自己添加换行\n
      [root@server ~]# printf  "hello world"
      hello world[root@server ~]# printf  "hello world\n"
      hello world
      #使用格式替代符打印
      [root@server ~]# printf "%d %s\n" 1 "abc"
      1 abc
      [root@server ~]# printf "%10s %-3d %-4.2f\n"  "zhangsan" 9 130.23 "lisi" 10 130.5
        zhangsan 9   130.23
            lisi 10  130.50
      # %s %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%f 输出实数,以小数形式输出。
      #%10s 字符串占10个字符宽度(- 表示左对齐,没有则表示右对齐),如果不足则自动以空格填充,超过也会将内容全部显示出来。
      #%-3d 数字占3个宽度(- 表示左对齐,没有则表示右对齐)
      # %-4.2f 格式化为小数,其中 .2 指保留2位小数
      

    脚本中一般是一行一条命令,而且是按照顺序执行每条命令。

    可以用以下符号在一行连接多条命令:;&&|||

    #先执行分号前的date命令再执行分号后面的命令
    [root@server ~]# date ; ls -l /etc/passwd
    #先执行&&符号前面的命令,如果前面命令执行成功则执行&&后面的命令;如果前面命令执行失败、报错,则不执行后面的命令
    [root@server ~]# mkdir /mnt/iso && mount /dev/sr0 /mnt/iso
    #先执行||符号前面的命令,如果前面命令执行成功则不执行后面的命令;如果前面命令执行失败、报错,则执行后面的命令
    [root@server ~]# mkdir  tt  ||  ls  /
    #管道符|:管道左边的命令的输出作为管道右边命令的输入
    #例如显示剩余内存大小
    [root@server ~]# free  -h | grep Mem | tr -s " " | cut -d " " -f4
    [root@server ~]# free  -h | awk '/Mem/ {print $4}'   # 使用awk截取第4列
    

[!Note]

shell中没有强制要求缩进,但是建议缩进,可以提高阅读性,程序更有层次感。

1.3.2执行shell脚本

  • 输入脚本文件的路径(绝对路径或者相对路径都可)执行脚本文件:需要x执行权限
  • bash 脚本文件
  • source 脚本文件
  • . 脚本文件

[!NOTE]

前两种方法都是启动一个子shell,在子shell中执行此脚本,脚本中设置的变量在脚本执行完毕后不会保存。

后两种方法是在当前shell进程中执行此脚本,而不是重新启动一个shell 在子shell进程中执行此脚本,并且脚本中设置的变量在脚本执行完毕后会保存下来。

#使用绝对路径或者相对路径方式执行脚本需要x执行权限
[root@server ~]# mkdir /shell
[root@server ~]# vim /shell/test.sh
#!/bin/bash
echo "hello world"
[root@server ~]# /shell/test.sh
-bash: /shell/test.sh: 权限不够
[root@server ~]# cd /shell/
[root@server shell]# ./test.sh
-bash: ./test.sh: 权限不够
[root@server shell]# bash /shell/test.sh
hello world
[root@server shell]# source  /shell/test.sh
hello world
[root@server shell]# . /shell/test.sh
hello world
[root@server shell]# chmod a+x /shell/test.sh
[root@server shell]# /shell/test.sh
hello world
[root@server shell]# ./test.sh
hello world
#前两种方式会产生子shell,脚本中的语句只在子shell中有效,当前shell中无效
[root@server shell]# vim /shell/test1.sh
#!/bin/bash
cd /etc
#执行脚本后,当前shell的工作目录未发生任何变化,因为只是在子shell中执行了语句,不会影响当前shell
[root@server shell]# chmod a+x /shell/test1.sh
[root@server shell]# /shell/test1.sh
[root@server shell]# bash /shell/test1.sh

#source和.执行方式脚本中的语句会在当前shell中生效
[root@server shell]# source /shell/test1.sh
#可以看到执行完脚本后工作目录发生了变化
[root@server etc]#
[root@server etc]# cd /shell/
[root@server shell]# .  /shell/test1.sh
[root@server etc]#

1.3.3脚本的退出状态码

  • linux的命令执行后就会有一个退出状态码,可使用echo $?查看命令的退出状态码;

  • 退出状态码范围:0-255,通常情况下,执行成功的命令返回0,不成功的命令返回非0值;

  • 脚本执行完毕后的退出状态码取决于脚本的最后一条命令,也可以使用exit 0-255指定脚本的退出状态码,可使用echo $?查看脚本的退出状态码;

  • 函数也可以返回一个退出状态码,使用return 0-255返回函数的状态码,可使用echo $?查看函数的退出状态码。

#命令执行后的状态码
[root@server ~]# echo "hello world"
hello world
[root@server ~]# echo $?
0
[root@server ~]# ls /root/hahaha
ls: 无法访问 '/root/hahaha': 没有那个文件或目录
[root@server ~]# echo $?
2
#脚本执行后的状态码
[root@server ~]# vim /shell/test.sh
#!/bin/bash
echo "hello world"
[root@server ~]# bash /shell/test.sh
hello world
[root@server ~]# echo $?
0
[root@server ~]# vim /shell/test.sh
#!/bin/bash
echo "hello world"
ls /root/hahaha
[root@server ~]# bash /shell/test.sh
hello world
ls: 无法访问 '/root/hahaha': 没有那个文件或目录
[root@server ~]# echo $?
2
[root@server ~]# vim /shell/test.sh
#!/bin/bash
echo "hello world"
ls /root/hahaha
exit 1
[root@server ~]# bash /shell/test.sh
hello world
ls: 无法访问 '/root/hahaha': 没有那个文件或目录
[root@server ~]# echo $?
1

[!note]

常见状态码:

 0----------------命令运行成功
 1----------------通知未知错误
 2----------------误用shell命令
 126--------------命令不可执行
 127--------------没有找到命令
 128--------------无效退出参数
 128+x------------linux信号x的严重错误
 130--------------命令通过Ctrl+C终止
 255--------------退出状态码越界
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐