一、Linux 命令和SHELL概念

在计算机中,有很多概念需要重新理解一下。

  • 命令行界面 (CLI) = 使用文本命令进行交互的用户界面
  • 图形用户界面 (GUI) = 使用图形界面进行交互
  • 终端 (Terminal) = TTY = 文本输入/输出环境
  • 控制台 (Console) = 一种特殊的终端
  • Shell = 命令行解释器,执行用户输入的命令并返回结果

命令行界面CLI

命令行界面,通俗来讲,就是我们看到的那种满屏幕都是字符的界面。用户主要使用命令来控制计算机。

命令行界面(英语:Command-line Interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行 —— 摘自 Wikipedia

在图形化桌面出现之前,与 Unix 系统进行交互的唯一方式就是借助由 shell 所提供的文本命令行界面(command line interface,CLI)。

CLI 只能接受文本输入,也只能显示出文本和基本的图形输出。

控制台终端Terminal

在 Linux 命令行界面模式中,开机后显示器出现的命令行界面(shell CLI)就叫终端,这种模式称作 Linux 控制台,因为它仿真了早期的硬接线控制台终端,而且是一种同 Linux系 统交互的直接接口。

大多数 Linux 发行版会启动5~6个(有时会更多)虚拟控制台(tty),虚拟控制台是运行在Linux系统内存中的终端会话。你在一台计算机的显示器和键盘上就可以访问它们。

tty和pts的区别 https://zhuanlan.zhihu.com/p/97018747

Ctrl +Alt 组合键配合 F1 或 F7 来进入虚拟机控制台终端或图形界面。

命令行终端中,默认的背景色是黑色的,白色字体,但是这些特性都可以调整,使用 setterm 命令调整。

  1. # 设置背景色
  2. setterm -background black red green yellow blue magenta cyan white
  3. # 设置前景色
  4. setterm -foreground black red green yellow blue magenta cyan white
  5. # 交换背景色和前景色
  6. setterm -inversescreen on off
  7. # 恢复默认
  8. setterm -reset
  9. setterm -store

图形化终端(终端模拟器)

但是在图形界面时,如果我也想使用命令行来操作 Linux 系统时,这样在图形界面中就需要 终端模拟器(terminal-emulators),要想在桌面中使用命令行,关键在于终端模拟器。

可以把终端模拟器看作GUI中(in the GUI)的CLI终端,将虚拟控制台终端看作GUI以外(outside the GUI)的CLI终端。

在 Linux 发行版上,最好用的终端模拟器:

  1. https://gnometerminator.blogspot.com/p/introduction.html

图形界面

图形用户界面,我们常见的 Windows 操作就是图形界面,用户主要使用图形界面来控制计算机。

命令行比图形界面在很多方面有一些优势,比如:我要把当前目录下的(包括嵌套的子目录)所有 *.tpl 文件的后缀名修改为 *.blade.php 。在图形界面操作时,就不是那么方便了,使用命令行,只需要一句命令就可以搞定:

  1. rename 's/\.tpl$/\.blade.php/' ./**/*.tpl

SHELL

Shell 是 Linux 下的命令交互程序,其实就是一个命令解释器。它用来接收用户输入的指令,传递给内核执行。

所以它可以被理解为内核外面的一层壳,用户通过它来与内核交互。

它虽然不是 Unix/Linux 系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。

因此,对于用户来说,shell 是最重要的实用程序,深入了解和熟练掌握 shell 的基本特性及其使用方法,是用好 Unix/Linux 系统的关键。

SHELL的分类

Linux 的 Shell 种类众多,常见的有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
    是UNIX最初使用的 shell,而且在每种 UNIX 上都可以使用。Bourne Shell 在 shell 编程方面相当优秀,但在处理与用户的交互方面做得不如其他几种 shell。
  • Bourne Again Shell(/bin/bash)
    • Linux 默认的 shell 它是 Bourne Shell 的扩展。 与 Bourne Shell 完全兼容,并且在 Bourne Shell 的基础上增加了很多特性,可以提供命令补全,命令编辑和命令历史等功能。
    • 基本上现在各大Linux发行版都是使用 bash 做为默认 shell
  • C Shell(/usr/bin/csh)
    是一种比 Bourne Shell 更适合的变种 Shell,它的语法与 C 语言很相似。
  • K Shell(/usr/bin/ksh)
    集合了 C Shell 和 Bourne Shell 的优点并且和 Bourne Shell 完全兼容。

这里演示用的是 Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。

在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash

我们可以通过 /etc/shells 文件来査询 Linux 支持的 Shell。命令如下:

  1. $ cat /etc/shells
  2. /bin/sh
  3. /bin/bash
  4. /sbin/nologin
  5. /bin/tcsh
  6. /bin/csh
  7. # 查看bash版本
  8. # CentOS Linux release 7.6.1810
  9. $ bash --version
  10. GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
  11. Copyright (C) 2011 Free Software Foundation, Inc.
  12. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  13. This is free software; you are free to change and redistribute it.
  14. There is NO WARRANTY, to the extent permitted by law.
  15. # Ubuntu 20.04.1 LTS
  16. $ bash --version
  17. GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
  18. Copyright (C) 2019 Free Software Foundation, Inc.
  19. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  20. This is free software; you are free to change and redistribute it.
  21. There is NO WARRANTY, to the extent permitted by law.

用户信息文件 /etc/passwd 的最后一列就是这个用户的登录 Shell。命令如下:

  1. $ cat /etc/passwd
  2. root:x:0:0:root:/root:/bin/bash
  3. bin:x: 1:1 :bin:/bin:/sbin/nologin
  4. daemon:x:2:2:daemon:/sbin:/sbin/nologin
  5. ...

可以看到,root 用户和其他可以登录系统的普通用户的登录 Shell 都是 /bin/bash,也就是 Linux 的标准 Shell,所以这些用户登录之后可以执行权限允许范围内的所有命令。

不过,所有的系统用户(伪用户)因为登录 Shell 是 /sbin/nologin,所以不能登录系统。

shell 的类型和场景

由于使用场景的不同,Shell 被分为两个类型:

  • login / non-login
  • interactive / non-interactive

这两个类型影响的是 Shell 的启动文件 (startup files)

当我们使用终端登录一台主机时,主机会为我们启动一个 Shell,由于是登录以后启动的,所以是 login Shell。

其他情况的 Shell 就是 non-login 的,比如我登录以后,输入 bash 再启动一个 Shell,那么这个 Shell 就是 non-login 的。

  1. # 举个简单的例子,我们在windows中使用终端工具ssh远程到一个Linux后,通常输入exit,会断开当前ssh连接。
  2. # 如果我们连上去后,多输入几次bash,即多启动几个shell,然后每次输入exit其实是退出当前shell而已,并没有很快断开ssh连接

login Shell 会初始化一些针对整个登录会话的任务。

比如说,我希望我每次登录主机,就自动发一封邮件出去,那么这个任务就可以在 login Shell 的启动文件中完成。

命令

Linux 命令分为两种类型:

应用程序命令,一般都会有相应的二进制可执行文件,通常存在 /bin , /usr/sbin/ , /usr/bin 等目录中。

shell 通过读取 $PATH 这个环境变量来查找应用程序执行路径。

通过 type 命令来查看命令是 shell 内建命令,还是二进制程序(如果是二进制可执行文件,还能打印出所在路径)。

  1. $ type ls
  2. ls is aliased to `ls --color=auto'
  3. $ type pwd
  4. pwd is a shell builtin
  5. $ type w
  6. w is /usr/bin/w
  7. $ type find
  8. find is /usr/bin/find

通过 whereis 命令来查看二进制文件的存放路径。

  • 交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。
  • 批处理(Batch):用户事先写一个 Shell 脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完。

SHELL脚本

SHELL 脚本,其实就是将一大堆可执行命令放在一个文本文件中,其中也可以包含一些逻辑判断,循环遍历等,就类似一种批处理。

脚本的执行

shell脚本有种执行方式:

  • bash ScriptName.sh 或 sh ScriptName.sh
    这种方式执行,脚本中可以没有执行权限,也可以没指定解释器
  • ./ScriptName.sh 或 /path/ScriptName.sh
    这种方式执行,需要脚本有可执行权限,需要在脚本中指定解释器。
  • source test.sh 或 . test.sh
    source 的意思是读入或加载指定的 shell 脚本文件,然后依次执行其中的所有语句。当在脚本中调用其他脚本时,通常使用 source 命令,因为这样子脚本是在父脚本的进程中执行的(其他方式都会启动新的进程执行脚本),因此,使用这种方式可以将子脚本中的变量值或函数等返回值传递到当前父脚本中使用。这是最重要的区别
  1. # 其实就是把 echo 'hello,fengzhao' 这行命令放到文本文件中,调用 shell 解释器来执行里面的命令
  2. root@vpsServer:~# cat test.sh
  3. echo 'hello,fengzhao'
  4. root@vpsServer:~#
  5. root@vpsServer:~# sh test.sh
  6. hello,fengzhao
  7. root@vpsServer:~#
  8. root@vpsServer:~# bash test.sh
  9. hello,fengzhao
  10. root@vpsServer:~#
  11. root@vpsServer:~# source test.sh
  12. hello,fengzhao
  13. root@vpsServer:~#
  14. root@vpsServer:~# . test.sh
  15. hello,fengzhao
  16. root@vpsServer:~#
  17. # test2中把pwd命令执行结果赋给变量home_dir,用bash执行是起新进程执行的,所以无法获取其变量值。
  18. # 用source执行,当前shell可以读到其返回值,并且立马生效。
  19. # 在实际生产中应用场景,比如我们修改了一些系统配置,想立马生效,就使用source来执行一下。
  20. root@vpsServer:~# echo home_dir=`pwd` > test2.sh
  21. root@vpsServer:~# bash test2.sh
  22. root@vpsServer:~# echo $home_dir
  23. root@vpsServer:~# source test2.sh
  24. root@vpsServer:~# echo $home_dir
  25. /root
  26. root@vpsServer:~#

变量

变量是暂时存储数据的地方及标记,所存储的数据位于内存空间中,通过正确的调用内存中变量的名字可以取出其变量值。

使用 ![](https://g.yuque.com/gr/latex?VARIABLES%20%E6%88%96%20#card=math&code=VARIABLES%2A%2A%20%E6%88%96%20%2A%2A){VARIABLES} 来引用变量。应用程序运行时通过读取环境变量

  1. # 声明变量 NAME='VALUE',变量名大写,变量值用引号括起来,防止值中有空格
  2. root@vpsServer:~# NAME='fengzhao'
  3. # 引用变量加上引号,防止变量名中有空格,${VARIABLES}
  4. root@vpsServer:~# echo ${NAME}
  5. fengzhao
  6. root@vpsServer:~#

根据变量的作用范围,变量的类型可以分为两类:环境变量(全局变量)和普通变量(局部变量)。

  • 环境变量:可以在创建它们的 shell 及其派生出来的任意子进程 shell 中使用,环境变量又分为用户自定义环境变量和 bash 内置环境变量。
  • 普通变量:只能在创建他们的 shell 函数内和 shell 脚本中使用。普通变量一般由开发者在开发脚本时创建。

根据变量的生命周期,可以分为两类:

  • 永久的:需要修改配置文件,变量永久生效。(这种变量需要在文件中声明)
  • 临时的:使用 export 命令声明即可,变量在关闭 shell 时失效。(这种变量在命令中声明)

环境变量

环境变量一般是值用 export 命令导出的变量,主要目的是用于控制计算机内所有程序的运行行为,比如:定义 shell 的运行环境等等。

  1. # 在命令行中定义一个环境变量,这种设置的变量不会持久化,只能在当前shell中有效,退出即失效。
  2. export VIRABLES='value'
  3. # 将变量定义在 ~/.bashrc 文件中,每一个 non-login interactive shell 在启动时都会首先读入这个配置文件中的变量
  4. # 将变量定义在 ~/.profile 文件中,每一个login shell 在登录时会读入 ~/.profile(一般在此文件中包含 .bashrc 文件)
  5. # 查看
  6. set
  7. #
  1. # 这是 ~/.profile 文件,在 Ubuntu中叫 ~/.profile ,在 CentOS 中叫 ~/.bash_profile
  2. # ~/.profile: executed by Bourne-compatible login shells.
  3. if [ "$BASH" ]; then
  4. if [ -f ~/.bashrc ]; then
  5. . ~/.bashrc
  6. fi
  7. fi
  8. # User specific environment and startup programs
  9. PATH=$PATH:$HOME/.local/bin:$HOME/bin
  10. mesg n || true
  1. # CentOS7中的 /etc/profile 文件
  2. # 这是系统全局环境变量文件,登录后的函数和别名在/etc/bashrc文件中。
  3. # 一般不要直接修改这个文件,除非你知道你在做什么,更好的办法是在 /etc/profile.d/ 中添加一个自己的文件 custom.sh
  4. # 这可以避免你添加的内容在系统升级时被覆盖掉。
  5. # /etc/profile
  6. # System wide environment and startup programs, for login setup
  7. # Functions and aliases go in /etc/bashrc
  8. # It's NOT a good idea to change this file unless you know what you
  9. # are doing. It's much better to create a custom.sh shell script in
  10. # /etc/profile.d/ to make custom changes to your environment, as this
  11. # will prevent the need for merging in future updates.
  12. # pathmunge大致的作用是:判断当前系统的PATH中是否有该命令的目录,如果没有,则判断是要将该目录放于PATH之前还是之后
  13. pathmunge () {
  14. case ":${PATH}:" in
  15. *:"$1":*)
  16. ;;
  17. *)
  18. if [ "$2" = "after" ] ; then
  19. PATH=$PATH:$1
  20. else
  21. PATH=$1:$PATH
  22. fi
  23. esac
  24. }
  25. if [ -x /usr/bin/id ]; then
  26. if [ -z "$EUID" ]; then
  27. # ksh workaround
  28. EUID=`/usr/bin/id -u`
  29. UID=`/usr/bin/id -ru`
  30. fi
  31. USER="`/usr/bin/id -un`"
  32. LOGNAME=$USER
  33. MAIL="/var/spool/mail/$USER"
  34. fi
  35. # Path manipulation
  36. if [ "$EUID" = "0" ]; then
  37. pathmunge /usr/sbin
  38. pathmunge /usr/local/sbin
  39. else
  40. pathmunge /usr/local/sbin after
  41. pathmunge /usr/sbin after
  42. fi
  43. HOSTNAME=`/usr/bin/hostname 2>/dev/null`
  44. HISTSIZE=1000
  45. if [ "$HISTCONTROL" = "ignorespace" ] ; then
  46. export HISTCONTROL=ignoreboth
  47. else
  48. export HISTCONTROL=ignoredups
  49. fi
  50. export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL
  51. # By default, we want umask to get set. This sets it for login shell
  52. # Current threshold for system reserved uid/gids is 200
  53. # You could check uidgid reservation validity in

bash 环境变量

命令历史

bash shell 有一个很有用的特性,那就是命令历史。

当前登陆用户在终端进行操作时,该用户操作的历史命令会被记录在内存缓冲区中,使用 history 命令可以列出历史命令。

当关闭终端会话或者会话退出时时,当前用户运行过的所有命令并写入当前用户的命令历史记录文件(~/.bash_history)。

如果是 zsh,在记录在 ~/.zsh_history 文件中。

命令历史一般都有很重要的作用,用来复盘查看以前执行过的命令,其实就是操作记录。所以一般入侵者,入侵离开之前,最后一步就是清理命令历史。

  1. # history 命令被用于列出以前执行过的命令
  2. # 在 oh-my-zsh 中。~/.oh-my-zsh/lib/history.zsh 这个脚本定义了一个函数 omz_history 封装 history,也可以用来查看命令历史。
  3. # 1.可以按一下上\下方向键,命令行就会显示相对于当前命令的上一条或下一条历史记录.
  4. # 2.和方向键相同功能的就是组合键Ctrl+ p (前面执行过的命令),Ctrl +n(后面执行过的命令).
  5. # 3.上面两个都是相对于当前命令查询上一条或者下一条命令的历史记录.如果搜索命令历史记录,
  6. # 就用 Ctrl+ r 组合键进入历史记录搜寻状态,然后,键盘每按一个字母,当前命令行就会搜索出命令历史记录.

命令历史的相关变量

BASH:相关的环境变量(ubuntu一般都在~/.bashrc中定义,centos一般都在/etc/profile中定义)

  1. # HISTSIZE:命令历史记录的最大条数。(内存缓冲区中),默认是1000
  2. # HISTFILE:命令历史文件存放路径。默认是 ~/.bash_history
  3. # HISTFILESIZE:命令历史文件记录的历史命令条数。默认是2000
  4. # HISTCONTROL:控制命令历史的记录方式。默认是ignorespace。如果想要让命令
  5. # ignoredups 忽略重复(连续且完全相同)的命令。
  6. # ignorespace 忽略以空白开头的命令,命令前面带空格
  7. # ignoreboth 表示以上两者都生效。
  8. HISTTIMEFORMAT="%F %T"
  9. # 这个值定义命令历史格式,一般设置成如下%F %T,可以方便的查看命令执行时间
  10. # 对于centos7,默认的命令历史命令参数都存在/etc/profile中,先查找相关参数的默认值
  11. [root@localhost ~]# egrep -R "HISTSIZE|HISTFILE|HISTFILESIZE|HISTCONTROL|HISTFORMAT" /etc/
  12. /etc/profile:HISTSIZE=1000
  13. /etc/profile:if [ "$HISTCONTROL" = "ignorespace" ] ; then
  14. /etc/profile: export HISTCONTROL=ignoreboth
  15. /etc/profile: export HISTCONTROL=ignoredups
  16. /etc/profile:export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL
  17. /etc/sudoers:Defaults env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS"

显示历史命令 (bash和zsh通用)

history :显示全部历史命令

history n :显示最后n条历史命令

history -c :清空历史命令(清空当前所有的命令历史)

history -a : 手动追加会话缓冲区的命令至历史命令文件中。(即不退出当前会话)

快捷操作(bash和zsh通用)

!# : 调用命令历史中的第#条命令

!string : 调用命令历史中的最近一条中以string开头的命令。

!! : 调用命令历史中上一条命令

命令历史清理

有时,我们使用敏感信息运行bash命令。 例如,运行一个shell脚本并传递密码作为命令行参数。 在这种情况下,出于安全原因,最好清除bash历史记录。

如果要完全删除bash历史记录,可以运行history -c命令。

如果要从bash历史记录中删除特定条目,请使用history -d offset命令。 偏移量是history命令输出的行号。

  1. [root@li1176-230 ~]# ls -1 | wc -l
  2. 3
  3. [root@li1176-230 ~]# history
  4. 1 history
  5. 2 ls
  6. 3 cd
  7. 4 ls -1 | wc -l
  8. 5 history
  9. [root@li1176-230 ~]# history -d 4
  10. [root@li1176-230 ~]# history
  11. 1 history
  12. 2 ls
  13. 3 cd
  14. 4 history
  15. 5 history -d 4
  16. 6 history
  17. [root@li1176-230 ~]#

SHELL脚本

SHELL脚本的执行

当 SHELL 以非交互式的方式执行时,它会先查找环境变量 ENV ,是

IO重定向和管道

在 Unix 中,一切皆文件。有 普通文件目录文件链接文件设备文件。 然而,进程访问文件数据必须要先 “打开” 这些文件。

内核为了跟踪某个进程打开的文件,则用一个个文件描述符组成了一个打开文件表

命令行运行的过程就是通过一些指令来处理某些数据或文件。

程序 = 指令 + 数据

读入数据:input

输出数据:output

文件描述符:每一个打开的文件,都有一个 fd(file descriptor)

文件描述符是与一个打开的文件或数据流相关的整数,文件描述符 0,1,2 是 Linux 系统预留的

  • 标准输入:keyboard(键盘),文件描述符用0表示。

标准输入:keyboard(键盘),文件描述符用0表示。

  • 标准输出:monitor(监视器),文件描述符用1表示。
  • 标准错误:monitor(监视器),文件描述符用2表示。

IO重定向:改变标准位置
  • 标准输出重定向:一般是把程序运行结果重定向到文件中。
    • command > new_position 覆盖重定向:覆盖文件,多次执行后,文件中只保留最后一次执行结果。
    • command >> new_position 追加重定向:追加文件,多次执行,每次都在文件中追加执行结果。
    • set -C : 禁止将内容覆盖重定向到已有文件中。当这个开启后,不允许直接对已有文件进行覆盖重定向。
    • 强制覆盖:command >| new_position ,不管有没有设置 set 。
      set +C : 打开之后,可以进行覆盖重定向。
  • 标准错误重定向:把错误输出流进行重定向
    • command 2> new_position 覆盖
    • command 2>> new_position 追加
  • 在一次命令执行过程中,标准输出和标准错误各自重定向至不同位置
    • 如果成功了,后面是空,如果失败了,前面是空
    • command > /path/file.out 2> /path/file.error
  • 将标准输出和标准错误重定向到同一个位置:
    command &> /path/file.out 追加
    command &>> /path/file.out 覆盖
    特殊用法: command > /path/file.out 2> &1

cat命令

预备知识

用一个单行命令将标准输入和文件中的数据合并,通常的解决办法是将stdin重定向到文件中,再将两个文件拼接在一起。更方便的做法是使用 cat 命令。

实战

cat 命令是一个日常经常使用的,cat 本身表示 concatenate (拼接):

  1. # cat 命令的help文档中是这么描述的:
  2. # concatenate files and print on the standard output
  3. # Concatenate FILE(s) to standard output.
  4. # With no FILE, or when FILE is -, read standard input.
  5. # 拼接文件并将其输出到标准输出,如果参数不是文件,则从标准输入读取
  6. # 所以cat命令的常规用法是这样的,打开两个文本文件,拼接后输出到标准输出
  7. cat file1.txt file2.txt
  8. # 如果cat后面跟的文件不存在,则报错:cat: file3.txt: No such file or directory
  9. cat file3.txt
  10. # 如果空输入一个cat,则光标一直闪烁,等待输入,输入一段字符,然后回车,则表示这段输入结束,cat就将其输出到标准输出。
  11. # 输入下面的命令后,光标闪烁,一直在等输入,然后依次输入1234回车换行,再输入5678,再 ctr+c 终止这次输入
  12. # cat > file 这个命令的本质是从标准输入中读取,然后将其重定向到文件。
  13. root@pve:~# cat > file3.txt
  14. 1234
  15. 5678
  16. ^C
  17. root@pve:~# cat file3.txt
  18. 1234
  19. 5678
  20. root@pve:~#

数组和关联数组

Bash 的数组,其元素的个数没有限制。数组的索引由 0 开始,但不一定要连续(可以跳号)。索引也可以是算术表达式。bash仅支持一维数组。

关联数组从 Bash4.0 开始引入。

Bash的数组,其元素的个数没有限制。数组的索引由0开始,但不一定要 连续(可以跳号)。索引也可以算术表达式。bash仅支持一维数组。

  1. #!/bin/bash
  2. # 数组用法
  3. # 声明数组:
  4. # 1、等号两边不要有空格
  5. # 2、数组元素间以空格分隔
  6. # 3、数组元素一般都是基本类型:数字,字符串等
  7. # 4、数组索引从 0 开始
  8. array_var=(1 2 3 4 5 6 'demo')
  9. # 按下标取数组中的元素:demo
  10. echo ${array_var[6])
  11. # 以清单形式打印数组中的所有值
  12. # 数组遍历方法一
  13. for var in ${array_var[@]};
  14. do
  15. echo $var
  16. done
  17. # 数组遍历方法二
  18. for(( i=0;i<${#array[@]};i++)) do
  19. #${#array[@]}获取数组长度用于循环
  20. echo ${array[i]};
  21. done;
  22. # 关联数组
  23. # 在关联数组中,索引下标可以是任意文本,而在普通数组中,只能用整数做数组索引。
  24. # 声明数组
  25. declare -A ass_array
  26. # 赋值方法1
  27. ass_array=( [index1]=value1 [index2]=value2)
  28. # 赋值方法2
  29. ass_array[index1]=value1
  30. ass_array[index2]=value2

shell 自动补全

SHELL脚本学习笔记

Shell 是 Linux 下的命令交互程序,其实就是一个命令解释器。

它用来接收用户输入的指令,传递给内核进行执行。所以它可以被理解为内核外面的一层壳,用户通过它来与内核交互。

它虽然不是 Unix/Linux 系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。

因此,对于用户来说,shell 是最重要的实用程序,深入了解和熟练掌握 shell 的基本特性及其使用方法,是用好 Unix/Linux 系统的关键。

可以说,shell 使用的熟练程度反映了用户对 Unix/Linux 使用的熟练程度。

1、Linux 命令和 SHELL 基础

Linux 命令分为两种类型:

  • 一类是 shell 内建命令;
  • 一类是应用程序命令。应用程序命令,一般都会有相应的二进制可执行文件,通常存在 /bin , /usr/sbin/ , /usr/bin 等目录中。
    shell 通过读取 $PATH 这个环境变量来查找应用程序执行路径。

通过 type 来查看命令是 shell 内建命令,还是二进制程序(如果是二进制可执行文件,还能打印出所在路径)。

  1. [root@fengzhao ~]# type ls
  2. ls is aliased to `ls --color=auto'
  3. [root@fengzhao ~]# type pwd
  4. pwd is a shell builtin
  5. [root@fengzhao ~]# type w
  6. w is /usr/bin/w
  7. [root@fengzhao ~]# type find
  8. find is /usr/bin/fi

通过 whereis 来查看二进制文件的存放路径。

  1. [root@fengzhao ~]# whereis find
  2. find: /usr/bin/find
  3. [root@fengzhao ~]#

1.1、shell有两种执行命令的方式

  • 交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。
  • 批处理(Batch):用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完。

1.2、几种常见的 Shell

Linux 发行版自带的标准 Shell 都是 Bash shell,Linux 的默认命令行就是 Bash,我们的最多的也是这个。

是 BourneAgain Shell 的缩写,内部命令一共有 40 个。一般日常使用 bash 基本上都够了,进阶可以试试 zsh。

另一个强大的 Shell 就是 zsh,它比 bash 更强大,但是也更复杂,配置起来比较麻烦。所以有个 on-my-zsh,它大大简化了 zsh 的配置,一般通过包管理器安装 zsh,然后通过 git 安装 on-my-zsh:

  1. $ git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
  2. $ cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。

Unix/Linux上常见的Shell脚本解释器有 bash、sh、csh、ksh、dash 等,习惯上把它们称作一种Shell。

1.3、查看操作系统的 Shell

  1. $ echo $SHELL # 查看当前使用的 shell
  2. $ cat /etc/shells # 查看当前系统支持的 shell
  3. $ bash # 切换至 bash ,输入 zsh 切换至 zsh。
  4. $ chsh -s /bin/zsh # 修改当前用户的 shell

1.4、Shell 配置文件

  • ~/.zshrc 和 ~/.bashrc:分别是当前用户的 bash 和 zsh 的配置文件,这个文件是用户级别的,当用户登陆打开终端后每一个shell进程生成时,执行的一组命令,包括设置别名,提示文本,颜色等设置。
  • ~/.bash_history 和 ~/.zshhistory 记录用户运行的历史命令。

1.5 、Shell 脚本的运行方式

  • 赋予脚本文件可执行权限,直接运行该脚本,系统靠文件第一行的 shebang 来确定解释器。(解释器可以是 py,bash,perl)
    比如,以 ./startup.sh 这样直接运行
  • 运行解释器,并将脚本文件名作为参数传入。
    比如,以 bash startup.sh 这样运行
  • 启动解释器,解释器从标准输入读取内容并执行(如果这个标准输入是用户键盘,那就是所谓的“交互式执行”)。
    比如,非常流行的直接运行网络上的脚本。```shell

    daocloud的设置镜像加速的脚本

    curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io

    rust官方推荐的安装脚本

    curl https://sh.rustup.rs -sSf | sh -s — —help

sh -s 用于从标准输入中读取命令,命令在子shell中执行

当sh -s 后面跟的参数,从第一个非 - 开头的参数,就被赋值为子shell的$1,$2,$3….

本质就是下载 set_mirror.sh 这个脚本,然后再执行 ./set_mirror.sh http://f1361db2.m.daocloud.io

  1. - 使用 source shellscript.sh . shellscript.sh 这种格式去运行。
  2. source 的意思是读入或加载指定的 shell 脚本文件,然后依次执行其中的所有语句。
  3. 当在脚本中调用其他脚本时,通常使用 source 命令,因为这样子脚本是在父脚本的进程中执行的(其他方式都会启动新的进程执行脚本)
  4. **因此,使用 source 这种方式可以将子脚本中的变量值或函数等返回值传递到当前父脚本中使用。这是与其他方式最重要的区别。**
  5. 所以我们在 /etc/profile 中声明一些环境变量时,想立马生效,就直接 source /etc/profile ,这样就对当前会话生效,而不需要重新登陆。
  6. 这种情况最多的用法就是 shell 脚本中调其他 shell 。在单个进程中执行的。
  7. <a name="7b4e49c3"></a>
  8. ## 2、变量
  9. 变量是暂时存储数据的地方及标记,所存储的数据位于内存空间中,通过正确的调用内存中变量的名字可以取出其变量值,使用 $VARIABLES ${VARIABLES} 来引用变量。
  10. > 注意,$(COMMAND) `COMMAND` 这种属于命令替换。不属于变量。
  11. 变量的类型可以分为两类:环境变量(全局变量)和普通变量(局部变量)。
  12. - 环境变量:可以在创建它们的 shell 及其派生出来的任意子进程 shell 中使用,环境变量又分为用户自定义环境变量和 bash 内置环境变量。
  13. - 普通变量:只能在创建他们的 shell 函数内和 shell 脚本中使用。普通变量一般由开发者在开发脚本时创建。
  14. 查看变量的命令:
  15. - set 输出所有变量,包括全局变量和局部变量
  16. - env 仅显示全局变量
  17. - echo $VARIABLES 打印VARIABLES变量值
  18. - cat /proc/$PID/environ 查看某个进程运行时的环境变量
  19. <a name="bc17b497"></a>
  20. ### 2.1、自定义环境变量
  21. 自定义环境变量有如下三种方法:
  22. ```shell
  23. # 给变量赋值,并导出变量,注意等号之间不能有空格
  24. fengzhao@fengzhao-pc:~$ VIRABLES=value; export VIRABLES
  25. fengzhao@fengzhao-pc:~$ echo $VIRABLES
  26. value
  27. fengzhao@fengzhao-pc:~$
  28. # 一句命令完成变量定义
  29. fengzhao@fengzhao-pc:~$ export VIRABLES2=value2
  30. fengzhao@fengzhao-pc:~$ echo $VIRABLES2
  31. value2
  32. fengzhao@fengzhao-pc:~$
  33. # 使用 declare 命令定义变量
  34. fengzhao@fengzhao-pc:~$ declare -x VIRABLES3=value3
  35. fengzhao@fengzhao-pc:~$ echo $VIRABLES3
  36. value3
  37. fengzhao@fengzhao-pc:~$
  38. # 把命令的结果赋值给某个变量,使用 VIRABLES="$(pwd)" 和 VIRABLES=`pwd` 两种方式
  39. fengzhao@fengzhao-pc:~$ DIRPATH="$(pwd)"
  40. fengzhao@fengzhao-pc:~$ echo $DIRPATH
  41. /home/fengzhao
  42. fengzhao@fengzhao-pc:~$
  43. fengzhao@fengzhao-pc:~$ BASEPATH=`basename /home/fengzhao`
  44. fengzhao@fengzhao-pc:~$ echo $BASEPATH
  45. fengzhao
  46. fengzhao@fengzhao-pc:~$

根据规范,所有环境变量的名字都定义成大写。在命令行中定义的变量仅在当前 shell 会话有效,一旦退出会话,这些变量就会失效,这样的就是普通变量。如果需要永久保存,可以在 ~/.bash_profile 或 ~/.bashrc 中定义。每次用户登陆时,这些变量都将被初始化。或放到 /etc/pfofile 文件中定义,这是全局配置文件。

2.2、变量定义技巧

前面的是介绍变量定义的命令,并不严谨。有几条基本准测:变量定义中,key=value 中的等号两边不能有任何空格。常见的错误之一就是等号两边有空格。

定义变量时,有三种定义方式,即 key=value , key=’value’ , key=”value” 这三种形式。

  • 不加引号,value中有变量的会被解析后再输出。
  1. fengzhao@fengzhao-pc:~$ DEMO1=1234$PWD
  2. fengzhao@fengzhao-pc:~$ echo $DEMO1
  3. 1234/home/fengzhao
  4. fengzhao@fengzhao-pc:~$
  • 单引号,单引号里面是什么,输出变量就是什么,即使 value 内容中有命令和变量时,也会原样输出。(适用于显示字符串,即 raw string )
  1. fengzhao@fengzhao-pc:~$ DEMO2='1234$PWD'
  2. fengzhao@fengzhao-pc:~$ echo $DEMO2
  3. 1234$PWD
  4. fengzhao@fengzhao-pc:~$
  • 双引号,输出变量时引号中的变量和命令和经过解析后再输出。(适用于字符串中附带有变量内容的定义)
  1. fengzhao@fengzhao-pc:~$ DEMO4="1234$(pwd)"
  2. fengzhao@fengzhao-pc:~$ echo ${DEMO4}
  3. 1234/home/fengzhao
  4. fengzhao@fengzhao-pc:~$
  5. fengzhao@fengzhao-pc:~$ DEMO5="1234$DEMO4"
  6. fengzhao@fengzhao-pc:~$ echo ${DEMO5}
  7. 12341234/home/fengzhao
  8. fengzhao@fengzhao-pc:~$
  • 反引号,一般用于命令,要把命令执行后的结果赋给变量
  1. root@vpsServer:~# echo `date`
  2. Tue Apr 7 21:26:16 CST 2020
  3. root@vpsServer:~#
  4. root@vpsServer:~# a=`date`
  5. root@vpsServer:~# echo ${a}
  6. Tue Apr 7 21:26:25 CST 2020
  7. root@vpsServer:~#
  8. # 第二种办法
  9. root@vpsServer:~# a=$(date)
  10. root@vpsServer:~# echo $a
  11. Tue Apr 7 21:40:53 CST 2020
  • 变量合并,多个变量合并在一起组成一个变量
  1. SOFRWARE_NAME="MySQL"
  2. VERSION="5.7.26"
  3. SOFTWARE_FULLNAME="${SOFTWARE_NAME}-${VERSION}.tar.gz"
  4. echo ${SOFTWARE_FULLNAME}
  5. MySQL-5.7.26.tar.gz
  • 变量使用
  1. 在使用变量的时候,一般用 ${VIRABLE_NAME}

2.3、常用环境变量

环境变量是操作系统中的软件运行时的一些参数,环境变量一般是由变量名和变量值组成的键值对来表示。应用程序通过读取变量名来获取变量值。通过和设置环境变量,可以调整软件运行时的一些参数。

最著名的操作系统变量就是 PATH 。在 windows 和 linux 都存在这个环境变量。它表示在命令行中执行命令时的查找路径。在 Linux 命令行中,可以通过 echo $VARIABLENAME 来查看变量值。

常用环境变量

  • $PATH 决定了shell将到哪些目录中寻找命令或程序(分先后顺序)
  • $HOME 当前用户主目录
  • $UID 当前用户的UID号,root用户的UID是0
  • $HISTSIZE 命令历史记录条数
  • $HISTTIMEFORMAT 命令历史时间格式 一般设置为” %F %T ‘whoami’”
  • $LOGNAME 当前用户的登录名
  • $HOSTNAME 指主机的名称
  • $SHELL 当前用户Shell类型
  • $LANGUGE  语言相关的环境变量,多语言可以修改此环境变量
  • $MAIL 当前用户的邮件存放目录

2.4、shell 中特殊且重要的变量

在 shell 中存在一些特殊的变量。例如:$0,$1,$2,$3… Linux-shell-foundmentals - 图1#,Linux-shell-foundmentals - 图2@ 等。这些是特殊位置参数变量。要从命令行,函数或脚本传递参数时,就需要使用位置变量参数。例如,经常会有一些脚本,在执行该脚本的时候,会传一些参数来控制服务的运行方式,或者启动关闭等命令的参数。在脚本里面,就使用这些变量来取执行的时候传递的参数。

变量值 含义
$0 当前执行的 shell 脚本文件名。如果执行时包含路径,那么就包含脚本路径。
$n 当前执行的 shell 脚本所有传递的第 n 个参数(参数以空格分开),如果 n>9 ,要用大括号引起来${10}
$# 当前执行的 shell 脚本传递的参数总个数。
$* 当前执行的 shell 脚本传递的所有参数。”$*” 表示将所有参数一起组成字符串。
$@ 也是获取所有参数,”$*” 表示将所有参数视为一个一个的字符串。
  1. #!/bin/bash
  2. ###############################################################################
  3. #
  4. # Copyleft (C) 2020 FengZhao. All rights reserved.
  5. # FileName:demo.sh
  6. # Author:FengZhao
  7. # Date:2020-02-18
  8. # Description:可以传参多个文件名做为参数,检查每个文件是否包含 "foobar" 字符串,如果没有,则在行尾加上 "# foobar"
  9. #
  10. ###############################################################################
  11. # Echo start time
  12. echo "Starting program at $(date)"
  13. echo "Running program $0 with $# arguments with pid $$"
  14. for file in "$@"; do
  15. grep foobar "$file" > /dev/null 2>&1
  16. if [[ "$?" -ne 0]]; then
  17. echo "File $file does not have any foobar , add one"
  18. echo "# foobar" >> "#file"
  19. fi
  20. done
  1. # 第一行打印执行该脚本时传递的前 15 个参数
  2. # 第二行打印执行该脚本时传递的总参数个数
  3. # 第三行打印执行该脚本时的文件名,此处是在脚本所在目录直接执行的。
  4. # 第四行打印执行该脚本时传递的总参数个数。
  5. fengzhao@fengzhao-pc:~$ cat demo.sh
  6. #! /bin/bash
  7. echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} ${14} ${15}
  8. echo $#
  9. echo $0
  10. echo $*
  11. fengzhao@fengzhao-pc:~$ bash demo.sh {a..z}
  12. a b c d e f g h i j k l m n o
  13. 26
  14. demo.sh
  15. a b c d e f g h i j k l m n o p q r s t u v w x y z
  16. fengzhao@fengzhao-pc:~$
  1. # 这个脚本,判断如果接收的参数个数不等于 2 ,就打印提示并退出,当前参数满足要求,就都打印参数。
  2. fengzhao@fengzhao-pc:~$ cat demo2.sh
  3. #! /bin/bash
  4. [ $# -ne 2 ] && {
  5. echo "USAGE $0 must two args"
  6. exit 1
  7. }
  8. echo $1 $2
  9. fengzhao@fengzhao-pc:~$ bash demo2.sh
  10. USAGE demo2.sh must two args
  11. fengzhao@fengzhao-pc:~$ bash demo2.sh arg1
  12. USAGE demo2.sh must two args
  13. fengzhao@fengzhao-pc:~$ bash demo2.sh arg1 arg2
  14. arg1 arg2
  15. fengzhao@fengzhao-pc:~$

生产常用代码段

生产中,在调一些脚本的时候,经常会对传参进行判断,并执行脚本中相应的函数。下面看一段 openresty(/etc/rc.d/init.d/openresty) 中的代码片段。这个脚本在执行的时候,可以接各种参数,如果匹配到下面这些参数,直接执行相应的函数

  1. case "$1" in
  2. start)
  3. rh_status_q && exit 0
  4. $1
  5. ;;
  6. stop)
  7. rh_status_q || exit 0
  8. $1
  9. ;;
  10. restart|configtest)
  11. $1
  12. ;;
  13. reload)
  14. rh_status_q || exit 7
  15. $1
  16. ;;
  17. force-reload)
  18. force_reload
  19. ;;
  20. status)
  21. rh_status
  22. ;;
  23. condrestart|try-restart)
  24. rh_status_q || exit 0
  25. ;;
  26. *)
  27. echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
  28. exit 2
  29. esac

2.5、SHELL 进程特殊状态变量

Linux shell 中存在一类特殊的进程状态变量。可以在命令中使用,也可以在脚本中使用。

变量 含义
$? 获取上一个命令的执行状态返回值(0为成功,非0为失败),可以在脚本中自定义。
$$ 获取当前执行的 shell 脚本的进程号(PID)
$! 获取上一个在后台工作的进程的进程号
$_ 获取在此之前执行的命令或脚本的最后一个参数

$? 变量常用场景

在 Linux 命令行中,执行一个复杂的命令或者没有输出的命令时,可以通过 echo $? 来打印这个命令是否执行成功。这个返回值的常规用法如下:

  • 判断命令,脚本或函数等程序是否执行成功。
  • 若在脚本中调用 “exit 数字”,则会把这个数字返回给 $? 。
  • 若是在函数中,调用”return 数字”把这个数字返回给 $? 。
  1. # demo3.sh ,当执行这个脚本时,判断传参数量是否等于2,如果不等于2,则执行 echo 语句,并且 exit 退出,返回 15 给 $?
  2. #! /bin/bash
  3. [ $# -ne 2 ] && {
  4. echo "must two arguments"
  5. exit 15
  6. }
  7. [root@fengzhao ~]# ./demo3.sh
  8. must two arguments
  9. [root@fengzhao ~]# echo $?
  10. 15
  11. [root@fengzhao ~]#

变量常用场景
**
变量表示当前 shell 进程号。有时在执行定时任务时频率较快,不知道上一个脚本是否执行完毕。但是业务要求同一时刻必须只有一个脚本在执行。
考虑如下实现思路:

把进程号重定向到文件中,在脚本头部,先检查该文件是否存在。如果存在,则杀掉进程,删除文件。

  1. # demo4.sh
  2. #! /bin/bash
  3. PIDPATH=/tmp/a.pid
  4. if [ -f "$PIDPATH" ]
  5. then
  6. kill `cat $PIDPATH` >/dev/null 2&>1
  7. rm -rf /tmp/a.pid
  8. fi
  9. # something you want to do
  10. echo $$ >$PIDPATH
  11. sleep 300
  12. # 睡眠300秒为了演示效果,
  13. # 后台运行脚本,运行多次,每次检查,都只有一个进程。
  14. $ ./demo4.sh &
  15. [2] 313
  16. $ ps -ef | grep demo4.sh | grep -v grep
  17. fengzhao 313 290 0 22:16 pts/0 00:00:00 /bin/bash ./demo4.sh
  18. [1]- Terminated ./demo4.sh
  19. $ ./demo4.sh &
  20. [3] 320
  21. $ ps -ef | grep demo4.sh | grep -v grep
  22. fengzhao 320 290 0 22:17 pts/0 00:00:00 /bin/bash ./demo4.sh
  23. [2]- Terminated ./demo4.sh
  24. ~$ ./demo4.sh &
  25. [4] 327
  26. $ ps -ef | grep demo4.sh | grep -v grep
  27. fengzhao 327 290 1 22:17 pts/0 00:00:00 /bin/bash ./demo4.sh
  28. [3]- Terminated ./demo4.sh