Linux Bash Shell

Shell是什么

操作系统管理着整个计算机的运行,协调着各部分的工作。操作系统的核心是被保护着的,如果我们想要和操作系统核心交互,就需要通过Shell(壳——就像核心外的一层外壳啦)来沟通(提供了使用操作系统的接口)。另外,我们还可以通过Shell调用其他的程序,让这些程序调用核心来完成我们的工作。Linux中的图形界面不属于系统的一部分,只算是一组应用程序,所以并不是万金油,很多时候我们依旧得使用Shell来控制系统。

BASH(Bourne Again SHell——sh(Bourne SHell)的增强版)就属于一种Shell程序,不同的SHell语法有区别,不过当前Linux默认使用的是sh/bash,系统中还有tcsh(cshell的强化版),不过我们还是先从bash学起吧。

用户登录后取得的shell记录在/etc/passwd中

[email protected]:/tmp# cat /etc/passwd |grep root
root:x:0:0:root:/root:/bin/bash

Bash shell的功能

bash提供的功能

bash注要相容于sh,并且有一些增强的功能。

  • 历史命令

    通过上下键可以调出历史用过的命令。记录在~/.bash_history中。

    • history n 列出最近的n条历史指令
    • -c 清空当前shell的历史指令
    • -a将目前新增的history指令记入histfiles文件中,如果没加文件名,默认使用 ~/.bash_history
    • -r从histfiles的内容读取到当前shell的history中
    • -w 将目前历史指令写入histfiles 覆写
    • !number 执行历史记录中的第number条指令
    • !comand 由最近的指令向前搜寻,找到以command开头的指令并执行
    • !! 执行上一条指令

如果一个账户同时打开了多个bash,那么最后一次登出的时候写入的history文件会覆盖之前的。

  • 命令与文件名补全

    使用tab可以补全命令与文件名,一串指令的第一个词里面使用tab为命令补全,第二个词后面则为文件名补全。通过bash-completion可以在某些指令后面使用tab补全选项/参数。

  • alias 命令别名设置

    使用个alias可以为我们的命令设置别名。 alias la='ls -al',还可以修改现有的指令alias rm='rm -i'这样写也是可以的。以bash登录系统时,系统会从~/.bash_history中读取之前的历史指令。变量HISTFILESIZE描述了会记录最近的多少条记录。

  • 工作控制

    可以让程序在后台执行。

  • 脚本

    shell scripts可以将我们需要使用的连续指令写到一个文件里面,批量执行,可以与用户交互式的进行,也可以依据参数和环境变量自动执行。

  • 万用字符与特殊符号

    bash提供的万用字符与特殊符号:

    万用符号:

    • * 代表0到无穷个任意字符
    • ? 代表一定有一个任意字符 ??? 名字为三个字符的文件
    • [] 代表一定有一个括号内的字符,[abcd]代表一定有一个字符是a,b,c,d内的任何一个。
    • [-],- 代表编码顺序中连续的所有字符 如 0-9,括号的作用和上面一样。ls /etc/*[0-9]*
    • [^],若[]中的第一个字符为^,则代表排除括号中的任意一个字符。(除a,b,c,d外都接受) ls /etc/[^a-z]* 排除小写字母开头的文件。

特殊符号:

  • # 注解
  • 转义
  • | 管线
  • ; 连续指令分隔
  • ~ 主文件夹
  • $ 取变量
  • & 后台运行程序
  • ! 非
  • / 目录符号
  • > ,>>,<,<< 数据重定向
  • '' 单引号,内部忽略取变量
  • "" 双引号,内部取变量$有效
  • () 中间包含子shell
  • {} 命令区块

bash的内置指令

bash内置了很多的指令,如cd umask等指令起始都是bash内置的指令,而不是外部的程序。

[email protected]:/tmp# type ls
ls is hashed (/bin/ls)
[email protected]:/tmp# type cd
cd is a shell builtin

[email protected]:/tmp# type -t ls
file
[email protected]:/tmp# type -t cd
builtin

对比一下可以外部指令和内建指令。

下达指令

  • 多行指令 使用\Enter来输入多行指令,回车一定要接着反斜杠。
  • ctrl+u 删除光标之前的指令 ctrl+k删除光标之后的指令
  • ctrl+a光标移动到指令串的最前面 ctrl+e 移动到最后后面

限制资源使用

之前不知道为什么linux会限制打开的文件数量,还会出现打开文件过多的错误。今天了解到还是有一定的必要加以限制的。考虑到linux是多任务多用户的系统,要是用户一多,并且同时打开了很多的比较大的文件,可能会消耗掉大量的内存,甚至多余主机的内存,这样会对系统造成很大的影响,所以bash可以通过ulimit限制使用者对于某些系统资源的使用。 包括打开的文件数量,可以使用的CPU时间,可以使用的内存总量。

-H 严格限制,必定不能超过这个数值
-S 警告设置,超过这个数值会有警告信息
-a 列出所有限制的额度
-c 程序发生错误时,系统可鞥将该程序在内存中的信息写成文件
-f shell可以创建的最大的文件大小
-d 陈旭可以使用的最大段内存的容量
-l 可以用于lock的内存量
-t 可占用的最大cpu时间
-u 单一使用者可以使用的最大进程数量

另外可以通过pam来管理使用者的ulimit限制。

Bash变量

linux作为多用户多任务系统,每个人登陆时都能取得一个shell,而每个人都有自己的环境变量。变量即用一个简单的字符串代表一个复杂或者经常变动的数据(当然你要是弄一个复杂的字符串也是可以的。),变量是可变的,所以程序可以不用把参数写死,可以接受系统中的变量,增加了灵活性。

某些环境变量还能影响到bash本身。如PATH变量,bash会在PATH变量中的路径中寻找可执行的程序指令。bash的变量提供了与系统进行交互时需要的部分参数。为了区分环境变量与普通变量,环境变量

一般使用大写。另外在shell脚本中也可以使用变量,放在脚本开始处,这样之后需要修改一项参数的时候就不用一一替换了。

变量的查看与修改

  1. 查看变量内容

    使用echo $var 可以查看变量var,$或${}的作用是取变量的内容。

  2. 变量赋值

    设用=连接变量与想要设置的内容就可以设置变量的内容了,等号两边不能有空格。变量名不能以数字开头,如果内容有空格需要使用引号括起来。双引号内的像是$这样的特殊字符还会保持它们原有的功能(可以使用对特殊符号进行转义 im= very hungry),单引号中的内容则仅当作纯文本处理。

    如果一串指令执行中还需要获得其他指令执行提供的信息,可以使用反引号将指令围起来或者使用$(指令),如 version=$(uname -r)

  3. 扩充变量

    如果想要在一个变量后面追加内容,可以在等号右边使用$取出变量并补充。

    PATH=${PATH}:/home/bin

  4. 如果变量需要在其他子程序中使用,需要用export使其变为环境变量运行脚本程序时,系统会创建一个子shell(新的进程,新的PID) ,普通变量只在被定义的shell内有效,而在子shell中无效,而export添加的环境变量则会在创建子shell时,在子shell中定义该变量的一个拷贝。 环境变量会被子程序继续引用。
  5. 取消变量

    unset 变量名称 可以清除一个变量。

  6. 另外在一串指令中反引号中的命令会优先执行,执行结果会作为外部输入的信息,类似于$()的作用。
  7. 变量持久化

    可以把变量写入bash配置文件 ~/.bashrc中,之后每次登录,这个变量都会被设置了。

环境变量的功能

使用env或者export可以查看当前shell环境的环境变量。

其中有一些环境变量是比较重要的,可以单独拿出来记录一下。

  • HOME

    代表使用者的主文件夹——cd ~进入的文件夹。

  • SHELL

    表明当前SHELL环境使用的是哪个SHELL程序,默认使用/bin/bash

  • MAIL

    表示使用mail收信时,系统回去读取哪个邮箱的文件。

  • PATH

    代表可执行文件的搜寻路径

  • LANG

    语系数据,某些程序会根据它来确定编码方式,遇见某些语系可能会出现程序无法解析当前语系编码的错误。

  • RANDOM

    代表着一个随机数的变量,通过/dev/random取得一个随机数,介于0~32767。declare -i number=$RANDOM*10/32768; echo $number可以得到一个-~9之间的数。

另外还可以使用set查看当前shell中的所有变量。除了环境变量,与shell操作接口相关/系统内设置的变量一般也设置成大写。下面这些是系统中比较重要的变量:

  • PS1

    命令提示符的格式变量,命令提示符即[email protected]:~# 这样的一串字符。

  • $

    $本身也是一个变量,表示当前Shell的PID(进程ID)

  • ?

    上一个执行的指令的返回值(return的值),如果正常执行,一般返回的是0,如果出错,得到的就是错误代码。

语系变量locale

locale -a可以查看我们的系统当前支持多少种语系。

[email protected]:~# locale -a
C
C.UTF-8
en_US.utf8
POSIX
zh_CN.utf8
zh_HK.utf8
zh_SG.utf8
zh_TW.utf8
#还可以单独修改下面的变量,在某些地方使用指定的语系显示
[email protected]:~# locale
LANG=en_US.UTF-8 # 主语言环境
LANGUAGE=en_US:en:zh_cn:zh
LC_CTYPE="en_US.UTF-8" #字符编码
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=en_US.UTF-8 #整体语系环境

如果没有单独设置,只要设置了LANG或者LC_ALL,那么未设置的部分就会被这两个变量取代,这两个是最主要的。Linux的终端接口tty只不支持中文显示,所以如果在tty里面修改成了中文,那中文部分全部都会变成乱码。

系统的默认语系设置位于/etc/locale.conf中(不过debian中好像不是这样)。

变量作用范围

环境变量基本可以看作全局变量,而自订的变量可以当作局部变量来看待。不过并不是只有环境变量才能传递到子shell中,“环境变量”不等于“bash的环境”,PS1并非环境变量,但是也会影响到bash的接口。

变量的键盘读取

可以让用户通过键盘输入来设置变量,还可以宣告这个变量的属性。

  • read

    使用read指令读取来自键盘输入的变量,一般用于脚本的编写。read -p 提示字符 -t 等待时间 变量名

  • declare/typeset

    用于宣告变量类型

    • -a 定义为数组

      array类型,var[index]=content 用于声明一串相关的变量

    • -i 定义为整数数字
    • -x 将后面的变量变成环境变量 +x 设置成非环境变量。
    • -r 变量设置成只读类型(需要重新登录才能恢复)

Bash操作环境

指令执行顺序

指令的搜寻与执行是有顺序的,从上到下优先级依次降低:

  1. 相对/绝对路径执行的指令
  2. 由alias找到的指令
  3. bash内置的指令
  4. 从$PATH变量指定的路径中,按顺序找到的第一个指令

登录时的欢迎信息

我们在登录某些系统的时候可以看到一串欢迎信息,他们是在/etc/issue以及/etc/motd里面设置的,issue里面可以使用反斜杠取用一些包含了系统信息的变量。文件夹下还有一个issue.net,那是在通过telnet登录时显示的信息。 而如果想在用户登录后告诉用户一些信息,可以编辑/etc/motd

环境配置文件

当我们登录bash后,默认就有了很多的环境变量,这些变量是在环境配置文件中设置的。bash在启动时会读取这些文件。配置文件分为全体系统配置文件个人偏好配置文件上面的alias,设置的变量,在登出bash后久会失效,只有把他们写入到配置文件中才能保留下来。

login与nologin shell

通过完整的流程取得的shell,称为login shell,而nologin shell则是不需要重复登录而取得的shell。 如:在bash中执行bash,会打开一个新的bash shell,或者在图形界面中打开一个bash shell,都属于nologin shell。

这两种shell读取的配置文件是不一样的。

login shell读取的配置文件

不通发行版的配置文件,配置文件中读取的配置文件可能会有一些区别,但是总体上的作用和意义是相同的。

login shell一般只读取下面的配置文件:

  1. /etc/profile ,系统的整体设置,除非要修改全局设置,不然不建议修改

    if [ "`id -u`" -eq 0 ]; then
      PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    else
      PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
    fi
    export PATH

    节选一部分,可以看到,该文件也是一个脚本,通过用户的UID设置不同的PATH。

    另外,/etc/profile里面还会调用外部的文件,比如观察可以看到,脚本中还依次执行了 /etc/profile.d/下的文件,进行颜色,语系,命令别名,bash命令补全等等参数的设置以及初始化。总的来说,虽然整体环境配置文件看上去只读取了/etc/profile,但是profile又会调用更多的配置文件。

  2. ~/.bash_profile 或者 ~/bash_login 或者 ~/.profile

    为什么用的是或者?因为bash login shell设置只会按照上面的顺序读取三个文件中的一个,读到了一个就不会读剩下的文件。

    这几个文件是属于使用者个人的配置文件,这个文件有时候可能也会调用外部的配置文件如.bashrc.在我的系统(debian10)中,/etc/profile与~/.profile中的内容都比较简单,大部分的设置都是在.bashrc中执行的。

bash是使用source指令来读取这些环境配置文件的,在修改配置文件后我们也可以使用source使这些配置文件生效,而无需重新登录。另外使用. ~/.bashrc也可以读入配置文件。我们也可以设置一些自定义的环境配置文件,在不同的工作需求下使用不同的环境配置。

nologin shell读取的配置文件

nologin shell在取得shell时仅会读取~/.bashrc配置文件。

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

看得出bashrc中也会调用其他的配置文件,root和一般用户的.bashrc也有着一定的区别。

其他配置文件

  • /etc/man_db.conf 或 /etc/manpath.config (debian中)

    规定了使用man的时候前往哪里寻找man page。

  • ~/.bash_history

    记录了历史输入指令

  • ~/.bash_logout (debian下没发现这个文件)

    记录了用户登出bash之后,系统需要执行的操作

终端机环境配置

我们登录终端的时候,会取得一些关于终端的输入环境的设置(如按键对应的功能),某些类unix系统中,可能会把删除字符设置为del而不是退格键,不过这个设置我们也是可以自己选修改的。这需要用到stty 设置终端命令来进行设置。另外bash也有一些关于终端机的设置,使用到set进行设置。

数据流重定向

标准输出与标准错误输出

标准输出指的是指令执行回传的正确信息,标准错误输出则为指令执行失败后回传的错误信息。这两个输出默认都是输出到屏幕上的,不过我们可以将stdout与stderr重定向到其他的地方(设备或者文件)。

  • 标准输入 stdin 代码为0, 使用<或者<<
  • 标准输出 stdout 代码为1, 使用>或者>> (表示追加而不是直接覆盖)
  • 标准错误输出 stderr 代码为2 使用 2>或者2>>

代码和符号之间不要加空格!。经常能看到的2>&1 表示错误输出等同于标准输出的重定向,不要使用1>file 2>file的写法,这样写的话,两种数据是同时写入文件的,可能会出现交叉写入/次序混乱的情况,文本中的顺序与实际屏幕上显示的顺序会不一样。 如果两种输出写到一个文件还可以写作 &>file

标准输入

原本的标准输入来自于键盘,使用该符号可以将其重定向到文件。

<< 用来表示结束输入的字符,遇到这个字符的时候,输入就会结束了。

重定向通常用途

重定向通常用于

  • 需要保存输出的信息
  • 后台执行程序,不希望输出干扰到屏幕
  • 想要丢弃一些不需要的信息 > /dev/null
  • 错误信息与正确信息分别输出

管道命令

管道命令|,将一个指令的标准输出(不包含stderr)作为下一个命令(需要能接受stdin)的输入。如ls /etc | less。如果想让管道能够处理stderr,可以使用2>&1,让stderr的输出等同于stdout。

管道命令通常配合下面这些命令使用:

配合使用的命令

减号 - 的用途

有时在用到了管道指令的地方我们还能看见减号的出现。

某些指令需要用到文件名称进行处理,如tar -cvf filename /home 将/home打包成filename,但是如果我们想要和管道一起用,那么这条指令我们就不需要它输出文件,此时该指令的stdout与管道后面的一条指令的stdin可以用 - 来替代。tar -cvf - /home | tar -xvf - -C /tmp。还有就是我们执行网络上的脚本时也很常见到减号的使用 .

数据撷取

  • cut

    cut可以将信息的某一段给切出来,以行为单位进行处理

    -d 后面接分隔符

    -f -d指定的分隔符把一段文字切为数段,-f指定取出第几段(从1开始) 用于处理使用了分隔符的数据。

    -c 以字符为单位取出固定的字符区间 -c 20- 20个字符后的数据 用于处理长度规整的数据。

    如”切开“ PATH变量 echo $PATH | cut -d ':' -f 3,5

  • grep

    用于从文本中取出想要的信息 grep [-acinv] [--color=auto] '搜索字符串' filename

    • -a 二进制文件以文本方式搜索数据
    • -c 计算找到要搜索的串的次数 (加了这个,输出的就是次数)
    • -i 忽略大小写
    • -n 输出行号
    • -v 反向选择,显示没有”搜索字符串“的行
    • --color=auto 搜索的字符串加上色彩标明
    • 还有很多支持的语法,比如正则表达式等等...

last | grep -n "root" 查看root登录的记录。

排序

  • sort [-fbMnrtuk] file

    -f 忽略大小写

    -b 忽略前面的空白字符

    -M 以月份的名字(JAN,DEC)来进行排序

    -n 以纯数字进行排序

    -r 反向排序

    -u unique,相同的数据仅出现一次

    -t 分隔符号 默认使用tab分隔

    -k 以指定区间进行排序 (与t结合,如很多行以:分隔的文本排序,若要依据第三部分来进行排序,sort -t ':' -k 3

  • uniq

    用于去除重复内容

    -i 忽略大小写

    -c 进行计数

    如获取每个账户的登录次数last | cut -d ' ' -f 1| sort|uniq -c (需要先排序)

    [email protected]:~# last | cut -d ' ' -f 1| sort | uniq -c
          1 
         27 pi
         24 reboot
         95 root
          1 wtmp
  • wc

    计算一个文本中有多少个字,多少行(默认全部输出)。

    -l 仅列出行

    -w 仅列出字符

    -m 多少字符

查看登录总人数的命令,去除了无关信息,空白,注意使用反向选择排除

last | grep [a-zA-Z] | grep -v 'wtmp' | grep -v 'reboot' | grep -v 'unknown' | wc -l

查看系统中有多少个账号

cat /etc/passwd | wc -l

双向重定向

> 会将数据流整个传送给文件或设备,所以我们重定向后就没办法继续利用这个数据流了,而使用tee可以让数据流保存一份到文件中之后继续利用数据流。

tee [-a] file,-a累加方式,将数据加入文件中。

last | tee last.list | cut -d " " -f1 将last的输出存到文件中的同时,还能继续利用数据流进行撷取操作。

字符转换指令

  • tr

    用来删除一段文本中的特定文字,或者替换。

    -d str 删除str -s 取代重复的字符。

    tr '[a-z]' '[A-Z]' 小写转大写(替换)

  • col

    很多时候可以用来将tab转为空格(-x参数)

  • join

    将两个文件中有相同数据的一行连接在一起。

  • paste

    不经过比对直接将两个文件中的,同一行合并在一起,以tab分开。

  • expand

    将tab转为空格

分区命令

split [-bl] file PREFIX该命令可以将一个大文件分为多个小文件(依据行数或者大小)

-b 分区成的文件大小 可用单位b k m等

-l 以行数来分区

之后想要合并的话就使用 cat PREFIX* >> siglefile 即可

参数替换指令

xargs可以读入stdin并以空格或者分隔符作为依据,将stdin的数据分隔成一个个参数。并作为后面的命令的参数(有些命令不支持管道命令,可以使用xargs间接的将管道传输过来的指令放到作为命令的参数)

find /usr/sbin -perm /7000 | xargs ls -l

命令执行

  • 一行执行多个命令

    使用;分隔

  • && 与 || 前后指令相关

    如果两个指令彼此之间是相关的,如后者指令的执行取决于前者的执行结果,就要用到这两个符号了。 后面的指令会受到前一个指令执行的返回值($?)影响。如

    • cmd1 && cmd2

      如果cmd1正确执行,接着执行cmd2,cmd1执行返回错误值($?不等于1),cmd2不执行。 比如在判断目录存在时创建文件

    • cmd1 || cmd2

      cmd1正确执行,cmd2不执行;cmd1执行失败才会继续执行cmd2 比如用于判断目录不存在时创建目录

混合起来, ls /tmp/files && echo "exist" || echo "not exist" ,注意两个符号的顺序是不能交换的。