xizhou

 1.前言

我们平时所说的 Shell 可以理解为 Linux 系统提供给用户的使用界面。Shell 为用户提供了输入命令和参数并可得到命令执行结果的环境。当一个用户登录 Linux 之后,系统初始化程序 init 就根据 /etc/passwd 文件中的设定,为每个用户运行一个被称为 Shell(外壳)的程序。

 

如图所示,Shell 是一个命令行解释器,处在内核与外层应用程序之间,起着协调用户与系统的一致性、在用户与系统之间进行交互的作用。Shell 接收用户输入的命令,并把用户的命令从类似 abed 的 ASCII 码解释为类似 0101 的机器语言,然后把命令提交到系统内核处理;当内核处理完毕之后,把处理结果再通过 Shell 返回给用户。

其实我们在 Linux 中操作的命令行界面就是 Linux 的 Shell,也就是 Bash,但是我们的图形界面是 Shell 吗?其实从广义讲,图形界面当然也是 Shell,因为它同样用来接收用户的操作,并传递到内核进行处理。不过,这里的 Shell 主要指的是 Bash。

2.Shell的分类

目前 Shell 的版本有很多种,如 Bourne Shell、C Shell、Bash、ksh、tcsh 等,它们各有特点

注意,Shell 的两种主要语法类型有 Bourne 和 C,这两种语法彼此不兼容。Boume 家族主要包括 sh、ksh、Bash、psh、zsh;C 家族主要包括 csh、tcsh(Bash 和 zsh 在不同程序上支持 csh 的语法)。

本文讲述的脚本编程就是在 Bash 环境中进行的。不过,在 Linux 中除了可以支持 Bash,还可以支持很多其他的 Shell。我们可以通过 /etc/shells 文件来査询 Linux 支持的 Shell。命令如下:

[root@localhost ~]# cat /etc/shells 
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
View Code

当然,这些 Shell 是可以任意切换的

[root@localhost ~]# sh
sh-4.2# exit
exit
[root@localhost ~]# 
View Code

用户信息文件 /etc/passwd 的最后一列就是这个用户的登录 Shell

[root@localhost ~]# grep \'root\' /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
View Code

root 用户和其他可以登录系统的普通用户的登录 Shell 都是 /bin/bash,也就是 Linux 的标准 Shell,所以这些用户登录之后可以执行权限允许范围内的所有命令。不过,所有的系统用户(伪用户)因为登录 Shell 是 /sbin/ndogin,所以不能登录系统。

3.常见的Linux命令

echo命令

echo 命令非常简单,命令的输出内容如果没有特殊含义,则原内容输出到屏幕;如果输出内容有特殊含义,则输出打印其含义。该命令格式如下:

[root@localhost ~]# echo [选项] [输出内容]

选项:

  • -e:支持反斜线控制的字符转换(具体参见表 1)
  • -n:取消输出后行末的换行符号(内容输出后不换行)

【例 1】

[root@localhost ~]# echo -n "hello world"
hello world[root@localhost ~]# 
#如果加入了 "-n"选项,则在输出内容结束后,不会换行,直接显示新行的提示符
View Code

在 echo 命令中如果使用了"-e"选项,则可以支持控制字符,如图所示。

【例 2】

 

[root@localhost ~]# echo -e \'a\tb\tc\nd\te\tf\'
a    b    c
d    e    f
[root@localhost ~]# 
View Code

 

history命令

Bash 有完善的历史命令,这对于简化管理操作、排查系统错误都有重要的作用,而且使用简单方便,建议大家多使用历史命令。系统保存的历史命令可以使用 history 命令查询,命令格式如下:

[root@localhost ~]# history [选项] [历史命令保存文件]

选项:

  • -c:清空历史命令;
  • -w:把缓存中的历史命令写入历史命令保存文件中。如果不手工指定历史命令保存文件,则放入默认历史命令保存文件 ~/.bash_history 中;

查看历史命令默认保存数量

[root@localhost ~]# grep \'HISTSIZE\' /etc/profile
HISTSIZE=1000
View Code

历史命令的调用

  • 使用上、下箭头调用以前的历史命令
  • 使用"!n"重复执行第 n 条历史命令
  • 使用"!!"重复执行上一条命令
  • 使用"!字符串"重复执行最后一条以该字符串开头的命令。
  • 使用"!$"重复上一条命令的最后一个参数。

【例】

[root@localhost ~]# history
…省略部分输出…
 1017  grep \'HISTSIZE\' /etc/profile
 1018  history
 1019  vim history
 1020  history
[root@localhost ~]# !1017
grep \'HISTSIZE\' /etc/profile
HISTSIZE=1000
export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL
[root@localhost ~]# vim !$
vim /etc/profile
View Code

alias命令

给命令设置别名,命令格式

[root@localhost ~]# alias
#查询命令别名
[root@1ocalhost ~] # alias 别名=\'原命令\'
#设定命令别名

在配置文件.bashrc中生成永久别名

[root@localhost ~]# ls -a
.                .bash_history  .bashrc  databases_backup_20190404194735  .mysql_history  .rnd     upgrade_lnmp_php20181210175959.log  upgrade_lnmp_php20181210180924.log
..               .bash_logout   .config  install-mphp5.6.log              .pearrc         sh       upgrade_lnmp_php20181210180234.log  upgrade_lnmp_php20181213095118.log
anaconda-ks.cfg  .bash_profile  .cshrc   lnmp-install.log                 .pki            .tcshrc  upgrade_lnmp_php20181210180801.log  .viminfo
[root@localhost ~]# vim .bashrc
View Code

删除别名

[root@localhost ~]# unalias 别名
View Code

 

命令执行顺序

  1. 第一顺位执行用绝对路径或相对路径执行的命令。
  2. 第二顺位执行别名。
  3. 第三顺位执行 Bash 的内部命令。
  4. 第四顺位执行按照 $PATH 环境变量定义的目录査找的第一条命令。

find和grep命令

find命令

find 命令用于在系统中搜索符合条件的文件名,如果需要模糊査询,则使用通配符进行匹配。搜索时文件名是完全匹配的,命令格式如下:

[root@localhost ~]# find 搜索路径 [选项] 搜索内容

find 是比较特殊的命令,它有两个参数:

  • 第一个参数用来指定搜索路径;
  • 第二个参数用来指定搜索内容

find命令的选项比较复杂,简要列出几种:

选项:

  • -name: 按照文件名搜索;
  • -iname: 按照文件名搜索,不区分文件名大小;
  • -size[+-]大小:按照指定大小搜索文件
  • -atime [+-]时间: 按照文件访问时间搜索
  • -mtime [+-]时间: 按照文改时间搜索
  • -ctime [+-]时间: 按照文件修改时间搜索
  • -uid 用户 ID:按照用户 ID 査找所有者是指定 ID 的文件
  • -gid 组 ID:按照用户组 ID 査找所属组是指定 ID 的文件
  • -user 用户名:按照用户名査找所有者是指定用户的文件
  • -group 组名:按照组名査找所属组是指定用户组的文件
  • -nouser:査找没有所有者的文件
  • -type d:查找目录
  • -type f:查找普通文件
  • -type l:查找软链接文件
  • -a:and逻辑与
  • -o:or逻辑或
  • -not:not逻辑非

【例1】按照修改时间搜索

[root@localhost ~]# find . -mtime -5
.
./.bash_history
./sh
./sh/count.sh
./sh/parameter.sh
./sh/parameter2.sh
./sh/variable.sh
./sh/read.sh
./.viminfo
[root@localhost ~]# 
View Code

find时间轴如图所示

"-5"指的是 5 天内修改的文件,"5"指的是前 5~6 天那一天修改的文件,"+5"指的是 6 天前修改的文件。

【例2】运用逻辑运算符

[root@localhost ~]# find.-size +2k -a -type f
#在当前目录下搜索大于2KB,并且文件类型是普通文件的文件
View Code

【例3】-exec选项

格式:

[root@localhost ~]# find 搜索路径 [选项] 搜索内容 -exec 命令2{}\;
[root@localhost test]# find.-perm 444 -exec ls -l {}\;
-r--r--r-- 1 root root 0 6月 17 11:05 ./test2
#使用"-exec"选项,把find命令的结果直接交给"ls -l"命令处理
View Code

"-exec"选项的作用是把 find 命令的结果放入"{}"中,再由命令 2 直接处理。在这个例子中就是用"ls -l"命令直接处理,会使 find 命令更加方便。

【例4】-ok选项

[root@localhost test]# find .-perm 444 -ok rm -rf{}\;
<rm…./test2>?y  <-需要用户输入y,才会执行
#我们这次使用rm命令来删除find找到的结果,删除的动作最好确认一下
View Code

"-ok"选项和"-exec"选项的作用基本一致,区别在于:"-exec"的命令会直接处理,而不询问;"-ok"的命令 2 在处理前会先询问用户是否这样处理,在得到确认命令后,才会执行。

grep命令

grep 命令用于在文件中搜索符合条件的字符串,如果需要模糊査询,则使用正则表达式进行匹配。搜索时字符串是包含匹配的。命令格式如下:

[root@localhost ~]# grep [选项] "搜索内容" 文件名

选项:

  • -A 数字:列出符合条件的行,并列出后续的 n 行;
  • -B 数字:列出符合条件的行,并列出前面的 n 行;
  • -c:统计找到的符合条件的字符串的次数;
  • -i:忽略大小写;
  • -n:输出行号;
  • -v:反向査找;
  • --color=auto:搜索出的关键字用颜色显示;

read命令

read 命令接收标准输入(键盘)的输入,或者其他文件描述符的输入。得到输入后,read 命令将数据放入一个标准变量中。命令格式如下:

[root@localhost ~]# read [选项] [变量名]

选项:

  • -p "提示信息":在等待read输入时,输出提示信息;
  • -t 秒数:read命令会一直等待用户输入,使用此选项可以指定等待时间;
  • -n 字符数:read命令只接收指定的字符数就会执行;
  • -s: 隐藏输入的数据,适用于机密信息的输入;

变量名:

  • 变量名可以自定义。如果不指定变量名,则会把输入保存到默认变量REPLY中;
  • 如果只提供了一个变量名,则将整个输入行赋予该变量;
  • 如果提供了一个以上的变量名,则输入行分为若干字,一个接一个地赋予各个变量,而命令行上的最后一个变量取得剩余的所有字;

 【例】

[root@localhost sh]# vi read.sh
#!/bin/bash
read -t 30 -p "Please input your name:" name
#提示"请输入姓名"并等待30秒,把用户的输入保存到变量name中
echo "Name is $name"
#看看变量"$name"中是否保存了你的输入
read -s -t 30 -p "Please enter your age:" age
#提示"请输入年龄"并等待30秒,把用户的输入保存到变量age中
#年龄是隐私,所以我们用"-s"选项隐藏输入
echo -e "n"
#调整输出格式,如果不输出换行,则一会儿的年龄输出不会换行
echo "Age is $age"
read -n 1 -t 30 -p "Please select your gender[M/F]:" gender
#提示"请选择性别"并等待30秒,把用户的输入保存到变量gender中
#使用"-n 1"选项只接收一个输入字符就会执行(无须按回车键)
echo -e "\n"
echo "Sex is $gender"
View Code

执行一下这个脚本:

[root@localhost sh]# chmod 755 read.sh
赋予执行权限
[root@localhost sh]#./read.sh
#执行脚本
Please input your name: zhang san
#在read的提示界面输入姓名
Name is zhang san
#"$name"变量中保存了我们的输入
Please enter your age:
#因为加入了"-s"选项,所以输入不会显示在命令行上
Age is 18
#"$age"变量中保存了我们的输入
Please select your gender[M/F]: M
#因为加入了"-n 1"选项,所以只能输入一个字符
Sex is M
# "$gender"变量中保存了我们的输入
View Code

source命令

source 命令会强制执行脚本中的全部命令,而忽略脚本文件的权限。该命令主要用于让重新配置的环境变量配置文件强制生效。命令格式如下:

[root@localhost ~]# source 配置文件
或
[root@localhost ~]#.配置文件

"."就是 source 命令,使用哪种方法都是可以的。原来修改了环境变量配置文件,如果要想让其生效,则必须注销或重启系统。现在只要使用 source 命令就可以省略注销或重启的过程,更加方便。

4.Shell脚本基本格式

Shell的基本结构

第一行"#!/bin/bash"

在 Linux 中,以"#"开头的一般都是注释,不过这句话是例外的。这句话的作用是标称我以下写的脚本使用的是 Bash 语法,只要写的是基于 Bash 的 Shell 脚本都应该这样开头。这就像在 HTML 语言中嵌入 PHP 程序时,PHP 程序必须用<??>包含起来。

不过,有一些比较喜欢钻研的人也会有疑问,他们在写 Shell 脚本时,不加"#!/bin/bash"这句话,Shell 脚本也可以正确执行。那是因为我们是在默认 Shell 就是 Bash 的 Linux 中编写的脚本,而且脚本是纯 Bash 脚本才能够正确执行。如果把脚本放在默认环境不是 Bash 的环境中运行,又或者编写脚本的不是纯 Bash 语言,而是嵌入了其他语言(如 Tcl 语言),那么这个脚本就不能 正确执行了。所以,大家记住我们的 Shell 脚本都必须以"#!/bin/bash"开头。

第二行:注释

在 Shell 脚本中,除"#!/bin/bash"这行外,其他行只要以"#"开头的都是注释。第二行就是我们这个脚本的注释,建议大家在写程序时加入清晰而详尽的注释,这些都是建立良好编程规范时应该注意的问题。

第三行:程序的主体

既然 echo 命令可以直接打印"c.biancheng.net",那么将这句话放入 Shell 脚本中也是可以正确执行的,因为 Linux 的命令是可以直接在脚本中执行的。

运行方式

Shell 脚本写好了,那么这个脚本该如何运行呢?在 Linux 中,脚本的运行主要有以下两种方法:

赋予执行权限,直接运行

这种方法是最常用的 Shell 脚本运行方法,也最为直接简单。就是赋予执行权限之后,直接运行。当然,运行时可以使用绝对路径,也可以使用相对路径。命令如下:

[root@localhost sh]# chmod 755 hello.sh
#赋予执行权限
[root@localhost sh]# /root/sh/hello.sh
c.biancheng.net
#使用绝对路径运行
[root@localhost sh]#./hello.sh
c.biancheng.net
#因为我们已经在/root/sh目录中,所以也可以使用相对路径运行
View Code

Shell 脚本是否可以像 Linux 系统命令一样,不用指定路径,直接运行呢?当然是可以的,不过需要进行环境变量的配置。这里大家只需要知道,我们自己写的 Shell 脚本默认是不能运行的,要么使用绝对路径,要么使用相对路径。

通过Bash调用运行脚本

这种方法也非常简单,命令如下:

[root@localhost sh]# bash hello.sh
c.biancheng.net
View Code

这种方法的意思是直接使用 Bash 去解释脚本中的内容,所以这个脚本也可以正常运行。使用这种方法运行脚本,甚至不需要脚本文件有"执行"权限,只要拥有"读"权限就可以运行了

5.Bash常用快捷键

在 Bash 中有非常多的快捷键,如果可以熟练地使用这些快捷键,可有效地提高我们的工作效率。只是快捷键相对较多,不太好记忆,这就要多加练习和使用。

6.Bash多命令执行顺序

在 Bash 中,如果需要让多条命令顺序执行,则有这样方法

【例1】";"多命令顺序执行

[root@localhost ~]# ls ; date; cd /user; pwd
anaconda-ks.cfg err.log install.log install.log.syslog list.log out.log
sh
#ls命令正确执行
2013年 10月 21 日星期一 11:35:57 CST
#date命令正确执行
-bash: cd: /user:没有那个文件或目录
#cd命令报错,因为没有/user目录
/root
#虽然cd命令报错,但是并不影响pwd命令的执行
View Code

【例2】“&&”逻辑与

[root@localhost ~]# cp /root/test /tmp/test && rm -rf/ root/test && echo yes
cp:无法获取"/rooWest"的文件状态(stat):没有那个文件或目录
#复制/root/test到/tmp/test,如果命令成功则删除原文件,并打印"yes"
#因为/root/test文件不存在,所以第一条命令执行不正确,第二和第三条命令也都不执行
[root@localhost ~]# ls /tmp/
#在/tmp/目录中并没有建立test文件
[root@localhost ~]# touch /root/test
#建立/root/test文件
[root@localhost ~]# cp /root/test /tmp/test && rm -rf/ root/test && echo yes
yes
#第一条命令正确执行后,第二和第三条命令都正确执行
#所以打印了"yes"
[root@localhost ~]# ll /root/test
ls:无法访问/root/test:没有那个文件或目录
#源文件/root/test消失,因为第二条命令rm正确执行
[root@localhost ~]# ll /tmp/test
-rw-r--r--. 1 root root 010月 2113:16/tmp/test
#在/temp/目录中正确建立了test文件
View Code

【例3】“||”逻辑或

[root@localhost ~]#ls /root/test && echo "yes"||echo "no"
ls:无法访问/root/test:没有那个文件或目录
no
#因为/root/test文件不存在,第一条命令报错,所以,第二条命令不能正确执行
#因为第二条命令执行错误,所以第三条命令正确执行,打印"no"
[root@localhost ~]# touch /root/test
[root@localhost ~]# ls /root/test && echo "yes" || echo "no"
/root/test
yes
#因为第一条命令正确执行,所以第二条命令正确执行,打印"yes"
#因为第二条命令正确执行,所以第三条命令执行错误
View Code

7.Bash管道符

在 Bash 中,管道符使用"丨"代表。管道符也是用来连接多条命令的,如"命令1丨命令2"。不过和多命令顺序执行不同的是,用管道符连接的命令,命令 1 的正确输出作为命令 2 的操作对象。这里需要注意,命令 1 必须有正确输出,而命令 2 必须可以处理命令 1 的输出结果;而且命令 2 只能处理命令 1 的正确输出,而不能处理错误输出。

【例1】

[root@localhost ~]# ll -a /etc/|more
View Code

【例2】

[root@localhost ~]# netstat -an | grep "ESTABLISHED"
#查询一下本地所有网络连接,提取包含 ESTABLISHED(已建立连接)的行
#就可以知道我们的服务器上有多少已经成功连接的网络连接
[root@localhost ~]# netstat -an | grep "ESTABLISHED" | wc-l
#如果想知道具体的网络连接数量,就可以再使用wc命令统计行数
View Code

8.Shell通配符

在 Bash 中,如果需要模糊匹配文件名或目录名,就要用到通配符

【例】

[root@localhost ~]# cd /tmp/
[root@localhost tmp]# rm -rf *
#进入临时目录,删除所有文件
#这个也是通配符,代表当前目录中的所有文件
[root@localhost tmp]# touch abc
[root@localhost tmp]# touch abcd
[root@localhost tmp]# touch 012
[root@localhost tmp]# touch 0abc
#建立几个测试文件
[root@localhost tmp]# ls *
012 0abc abc abed
#"*"代表所有的文件
[root@localhost tmp]# ls ?abc
0abc
#"?"匹配任意一个字符,所以会匹配 0abc,但是不能匹配 abc,因为"?"不能匹配空
[root@localhost tmp]# ls [0-9]* 012 0abc
#匹配任何以数字开头的文件
[root@localhost tmp]# ls [^0-9]* abc abcd
#匹配不以数字开头的文件
View Code

 9.Shell单引号、双引号和反引号

被单引号括起来的字符都是普通字符,就算特殊字符也不再有特殊含义;而被双引号括起来的字符中,"$"、"\"和反引号是拥有特殊含义的,"$"代表引用变量的值,而反引号代表引用命令。

从 POSIX 规范的角度来说,尽量使用 $(命令) 的方式来引用命令的输出,而不要使用反引号。

【例】

[root@localhost var]# echo ls
ls
[root@localhost var]# echo `ls`
adm cache crash db empty games gopher kerberos lib local lock log mail nis opt preserve run spool tmp yp
[root@localhost var]# echo $(ls)
adm cache crash db empty games gopher kerberos lib local lock log mail nis opt preserve run spool tmp yp
[root@localhost var]# echo \'ls\'
ls
[root@localhost var]# echo "ls"
ls
[root@localhost var]# echo "`ls`"
adm
cache
crash
db
empty
games
gopher
kerberos
lib
local
lock
log
mail
nis
opt
preserve
run
spool
tmp
yp
View Code

10.父Shell和子Shell

通过 pstree 命令査看一下进程数

[root@localhost var]# pstree
…省略部分输出
├─sshd─┬─sshd───bash───bash───pstree
        │      └─sshd───bash
…省略部分输出
View Code

可以看到我们的命令都是通过 ssh 远程服务链接的,在 ssh 中生成了第一个 Bash,就是父 Shell。因为我们刚刚执行了 Bash 命令,所以在第一个 Bash 中生成了第二个 Bash,这个 Bash 就是子 Shell,我们是在子 Shell 中运行命令 pstree 的。

关于父 Shell 和子 Shell,大家可以想象成在 Windows 中我们开启了一个"cmd"字符操作终端,那么 Windows 本身就是父 Shell,而"cmd"终端则是子 Shell;也可以理解为在一个操作界面中又开启了一个操作界面

11.Shell小括号和大括号

小括号和大括号主要区别在于:

  • () 执行一串命令时,需要重新开启一个子 Shell 来执行。
  • {} 执行一串命令时,在当前 Shell 中执行。
  • () 和 {} 都是把一串命令放田括号里面,并且命令之间用";"隔开。
  • () 最后一条命令可以不用分号。
  • {} 最后一条命令要用分号。
  • {} 的第一条命令和左括号之间必须有一个空格。
  • () 里的各命令不必和括号有空格。
  • () 和 {} 中括号里面的某条命令的重定向只影响该命令,但括号外的重定向则会影响到括号里的所有命令。
[root@localhost ~]# name=sc #在父 Shell 中定义 name 的值是 sc
[root@localhost ~]# (name=liming;echo $name)
liming
#如果用()括起来一串命令,那么这些命令都可以执行
#给name变量重新赋值,但是这个值只在子Shell中
[root@localhost ~]# echo $name
sc
#父Shell中name的值还是sc,而不是liming
[root@localhost ~]#{ name=liming;echo $name;} liming
#但是用大括号来进行一串命令的执行时,name变量的修改是直接在父Shell中进行的
#注意大括号的格式
[root@localhost ~]# echo $name
liming
#name变量的值已经被修改了
View Code

其实在执行一串命令时,如果使用的是小括号,则这串命令所做的修改只在子 Shell 中生效,一旦命令执行结束,回到父 Shell 中,这个修改就会丟失;而如果使用的是大括号,则此串命令直接在父 Shell 中执行,命令执行结束后,修改依然会生效。

12.Bash变量

 变量是计算机内存的单元,其中存放的值可以改变。变量可以定制用户本身的工作环境。使用变量可以保存有用信息,使系统获知用户相关设置。变量也可以用于保存暂时信息。

规范

1) 变量名可以由字母、数字和下画线组成,但是不能以数字开头。如果变量名是"2name",则是错误的

2) 在 Bash 中,变量的默认类型都是字符串型,如果要进行数值运算,则必须指定变量类型为数值型。

3) 变量用等号"="连接值,"="左右两侧不能有空格。因为在 Shell 中命令的执行格式是"命令 [选项] [参数]",如果在"="左右两侧加入空格,那么 Linux 会误以为这是系统命令,是会报错的。

4) 变量值中如果有空格,则需要使用单引号或双引号包含,如 test="hello world!"。双引号括起来的内容"$"和反引号者都拥有特殊含义,而单引号括起来的内容都是普通字符。

5) 在变量值中,可以使用转义符"\"。

6) 如果需要増加变量值,那么可以进行变量叠加。

7) 如果要把命令的执行结果作为变量值赋予变量,则需要使用反引号或 $() 包含命令。

8) 环境变量名建议大写,便于区分。

自定义变量

【例】

[root@localhost ~]# name=xiaoming
[root@localhost ~]# echo $name
xiaoming
#查看变量
[root@localhost ~]# set
BASH=/bin/bash
...省略部分输出...
name=xiaoming
[root@localhost ~]# unset name
View Code

环境变量

环境变量和用户自定义变量最主要的区别在于,环境变量是全局变量,而用户自定义变量是局部变量。用户自定义变量只在当前的 Shell 中生效,而环境变量会在当前 Shell 和这个 Shell 的所有子 Shell 中生效。如果把环境变量写入相应的配置文件,那么这个环境变量就会在所有的 Shell 中生效。

这是有区别的,如果环境变量不写入配置文件,那么当前 Shell 一旦终止,这个环境变量就会消失,而只有写入配置文件才会永久地在所有 Shell 中生效。

在 Linux 中一般通过环境变量配置操作系统的环境,如提示符、查找命令的路径、用户家目录等,这些系统默认的环境变量的变量名是固定的,我们只能修改变量的值。

当然,我们也可以手工定义环境变量,不过这些自定义的环境变量不能修改操作系统环境,其只是一个全局变量而已。

【例1】设置环境变量

#第一种设置方法,直接声明
[root@localhost ~]# export age=18
#第二种设置方法,先声明本地变量
[root@localhost ~]# gender=male
[root@localhost ~]# export gender
View Code

【例2】查看环境变量

[root@localhost ~]# export age=18
[root@localhost ~]# gender=male
[root@localhost ~]# export gender
[root@localhost ~]# name=ziming
[root@localhost ~]# set
BASH=/bin/bash
...省略部分输出...
age=18
gender=male
name=ziming
[root@localhost ~]# bash
#再调用一次bash,也就是进入子Shell
[root@localhost ~]# set
BASH=/bin/bash
...省略部分输出...
age=18
gender=male
#在子Shell中只能看到环境变量"age"和"gender",而不能查询到用户自定义变量"name"
View Code

【例3】删除环境变量

[root@localhost ~]# unset gender
#env 和 set 命令的区别是,set 命令可以查看所有变量,而 env 命令只能查看环境变量。
[root@localhost ~]# env |grep gender
View Code

PATH变量

还记得前面提到的命令的执行顺序,假如我们输入了一个程序名,如果没有写入路径,系统就会到 PATH 变量定义的路径中去寻找是否有可以执行的程序,如果找到则执行,否则会报"命令没有发现"的错误。

【例】

[root@localhost ~]# echo $PATH
/usr/local/php/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
#在/root/sh编辑一个shell脚本hello.sh
[root@localhost sh]# vim hello.sh
#!/bin/bash
echo \'hello world\'
[root@localhost sh]# chmod 755 hello.sh 
#在变量PATH的后面,加入/root/sh目录
[root@localhost sh]# PATH="$PATH":/root/sh
#可以看到hello.sh可以直接执行了
[root@localhost ~]# hello.sh
hello world
View Code

当然,这样定义的 PATH 变量只能临时生效,一旦重启或注销系统就会消失。如果想要永久生效,则需要写入环境变量配置文件

PS1变量

PS1 是一个很有意思的变量,是用来定义命令行的提示符的,可以按照我们自己的需求来定义自己喜欢的提示符。

PS1 可以支持以下这些选项:

  • \d:显示曰期,格式为"星期 月 日"。
  • \H:显示完整的主机名。如默认主机名"localhost.localdomain"。
  • \h:显示简写的主机名。如默认主机名"localhost"。
  • \t:显示 24 小时制时间,格式为"HH:MM:SS"。
  • \T:显示 12 小时制时间,格式为"HH:MM:SS"。
  • \A:显示 24 小时制时间,格式为"HH:MM"。
  • \@:显示 12 小时制时间,格式为"HH:MM am/pm"。
  • \u:显示当前用户名。
  • \v:显示 Bash 的版本信息。
  • \w:显示当前所在目录的完整名称。
  • \W:显示当前所在目录的最后一个目录。
  • \#:执行的第几条命令。
  • \$:提示符。如果是 root 用户,则会显示提示符为"#";如果是普通用户,则会显示提示符为"$"。

【例】

[root@localhost ~]# echo $PS1
[\u@\h \W]\$
[root@localhost ~]# PS1="[\u@\h \t]"
[root@localhost 18:45:06]
View Code

Bash位置参数变量

在 Linux 的命令行中,当一条命令或脚本执行时,后面可以跟多个参数,我们使用位置参数变量来表示这些参数。
其中,$0 代表命令行本身,$1 代表第 1 个参数,$2 代表第 2 个参数,依次类推。当参数个数超过 10 个时,就要用大括号把这个数字括起来,例如,${10} 代表第 10 个参数,${14} 则代表第 14 个参数。

[root@localhost sh]# ls count.sh
count.sh
View Code

如果执行这样一条命令,则 $0 的值就是 ls 命令本身,$1 的值就是 count.sh这个文件

【例1】

[root@localhost sh]# vim count.sh
#!/bin/bash
num1=$1
num2=$2
sum=$(($num1 + $num2)
echo $sum
#调用
[root@localhost sh]# bash count.sh 1 2
3
View Code

【例2】

View Code

【例3】

[root@localhost sh]# vim parameter2.sh
#!/bin/bash
for i in "$*"
  do
    echo "The parameters is:$i"
  done
x=0
for y in "$@"
  do
    echo "The parameter$x is:$y"
    x=$(($x + 1))
  done
echo "x is:$x"
#调用
[root@localhost sh]# bash parameter2.sh 1 2
The parameters is:1 2
The parameter0 is:1
The parameter1 is:2
x is:2
View Code

预定义变量

预定义变量是在 Shell 一开始时就定义的变量,这一点和默认环境变量有些类似。不同的是,预定义变量不能重新定义,用户只能根据 Shell 的定义来使用这些变量

【例】

[root@localhost sh]# vim variable.sh 
#!/bin/bash
echo "The current process is $$"
find /root -name hello.sh &
echo "The last one Daemon process is $!"
[root@localhost sh]# bash variable.sh 
The current process is 1231
The last one Daemon process is 1232
[root@localhost sh]# /root/sh/hello.sh
View Code

变量运算符

运算符优先级

【例1】加减乘除

[root@localhost ~]# aa=$(( (11+3)*3/2))
#虽然乘和除的优先级高于加,但是通过小括号可以调整运算优先级
[root@localhost ~]# echo $aa
21
View Code

【例 2】取模运算

[root@localhost ~]# bb=$(( 14%3))
[root@localhost ~]# echo $bb 2
#14不能被3整除,余数是2
View Code

【例 3】逻辑与

[root@localhost ~]# cc=$(( 1 && 0))
[root@localhost ~]# echo $cc
0
#逻辑与运算只有相与的两边都是1,与的结果才是1;否则与的结果是0
View Code

 Shell变量与内容测试

在脚本中,有时需要判断变量是否存在或是否被赋予了值,如果变量已经存在并且被赋予了值,则不改变变量;如果变量不存在或没有被赋值,则赋予其新值。这时我们就可以使用变量测试与内容置换。

【例1】

[root@localhost ~]# unset y
#删除变量y
[root@localhost ~]# x=${y-new}
#进行测试
[root@localhost ~]# echo $x
new
#因为变量y不存在,所以x=new
[root@localhost ~]# echo $y
#变量y还是不存在的
[root@localhost ~]#y=""
#给变量y赋值为空
[root@localhost ~]#x=${y-new}
#进行测试
[root@localhost ~]# echo $x
[root@localhost ~]# echo $y
#变量x和y都为空
如果变量y有值呢?
[root@localhost ~]#y=old
#给变量y赋值
[root@localhost ~]# x=${y-new}
#进行测试
[root@localhost ~]# echo $x
old
[root@localhost ~]# echo $y
old
#变量x和y的值都是old
View Code

【例2】

如果大括号内是"=",则又是什么情况呢?先测试一下变量 y 没有被设置的情况,如下:

[root@localhost ~]# unset y
#删除变量y
[root@localhost ~]# x=${y:=new}
#进行测试
[root@localhost ~]# echo $x
new
[root@localhost ~]# echo $y
new
#变量x和y的值都是new
View Code

一旦使用了"=",那么会同时处理变量 x 和 y,而不像例 1 那样只改变变量 x 的值。如果变量 y 为空,则又是什么情况呢?

[root@localhost ~]#y=""
#设定变量y为空
[root@localhost ~]#x=${y:=new}
#进行测试
[root@localhost ~]# echo $x
new
[root@localhost ~]# echo $y
new
#变量x和y的值都是new
View Code

一旦在大括号中使用":",那么变量 y 为空或者没有被设置,处理方法是一样的。如果变量 y 已经被赋值 了,则又是什么情况?

[root@localhost ~]# y=old
#给变量y赋值
[root@localhost ~]#x=${y:=new}
#进行测试
[root@localhost ~]# echo $x
old
[root@localhost ~]# echo $y
old
#变量x和y的值都是old
View Code

【例3】

再测试一下大括号中是"?"的情况。

[root@localhost ~]# unset y
#删除变量y
[root@localhost ~]#x=${y?new}
-bash: y: new
#会把值"new"输出到屏幕上
View Code

如果变量 y 已经被赋值了呢? 

[root@localhost ~]# y=old
#给变量y赋值
[root@@localhost ~]# x=${y?new}
#进行测试
[root@localhost ~]# echo $x
old
[root@localhost ~]# echo $y
old
#变量x和y的值都是old
View Code

Shell环境变量配置文件

在 Linux 系统登录时主要生效的环境变量配置文件有以下 5 个:

  • /etc/profile。
  • /etc/profile.d/*.sh。
  • ~/.bash_profile。
  • -/.bashrc。
  • /etc/bashrc。

这 5 个环境变量配置文件(/etc/profile.d/*.sh 是一系列的配置文件)在用户登录过程中会依次生效。不过需要注意,/etc/profile、/etc/profile_d/*.sh 和 /etc/bashrc 这三个环境变量配置文件会对所有的登录用户生效;而 ~/.bash_profile 和 ~/.bashrc 这两个环境变量配置文件只会对当前用户生效(因为每个用户的家目录中都有这两个文件)

环境变量配置文件读取流程

由 /etc/profile 文件调用 /etc/profile.d/*.sh 文件。该目录中所有以 .sh 结尾的文件都会被 /etc/profile 文件调用

由 /etc/profile 文件调用 ~/.bash_profile文件。~/.bash_profile 文件就没有那么复杂了,这个文件主要实现了两个功能:

  1. 调用了 ~/.bashrc 文件。
  2. 在 PATH 变量后面加入了":$HOME/bin"这个目录。也就是说,如果我们在自己的家目录中建立了 bin 目录,然后把自己的脚本放入"~/bin"目录中,就可以直接执行脚本,而不用通过目录执行了。

由 ~/.bash_profile 文件调用 -/.bashrc 文件。在 -/.bashrc 文件中主要实现了两个功能:

  1. 定义默认别名。笔者把自己定义的别名也放入了这个文件。
  2. 调用/etc/bashrc。

由 ~/.bashrc 文件调用 /etc/bashrc 文件。在 /etc/bashrc 文件中主要定义了如下内容。

  • PS1 变量:也就是用户的提示符。如果我们想要永久修示符,就要在这个文件中修改。
  • umask:定义 umask 默认权限。这个文件中定义的 umask 是针对"没有用户登录过程(不需要输入用户名和密码,比如从一个终端切换到另一个终端,或进入子Shell)"时生效的。如果是"有用户登录过程",则 /etc/profile 文件中的 umask 生效。
  • PATH 变量:会给 PATH 变量追加值,当然也是在"没有用户登陆过程"时才调用的。
  • 调用 /etc/profile.d/*.sh 文件,这也是在"没有用户登录过程"时才调用的。在"有用户登录过程"时,/etc/profile_d/*.sh 文件已经被 /etc/profile 文件调用过了。

这 5 个环境变量配置文件会被依次调用。如果是我们自己定义的环境变量,则应该放入哪个文件呢?如果你的修改是打算对所有用户生效的,那么可以放入 /etc/profile 环境变量配置文件;如果你的修改只是给自己使用的,那么可以放入 ~/.bash_profile 或 -/.bashrc 环境变量配置文件。

但是如果我们误删除了这些环境变量,比如删除了 /etc/bashrc 或 ~/.bashrc 文件,那么这些文件中的配置就会失效(~/.bashrc 文件会调用 /etc/bashrc 文件),我们的提示符就会变成下面这样:

-bash-4.1#

因为在 /etc/bashrc 文件中会设定 PS1 命令提示符,如果这个文件不存在或没有被调用,那么提示符就会是 Bash 最基本的样子。

注销时生效的环境变量配置文件

在用户退出登录时,只会调用一个环境变量配置文件,就是 ~/.bash_logout。这个文件默认没有写入任何内容,但是如果我们希望在退出登录时执行一些操作,比如清除历史命令、备份某些数据,就可以把命令写入这个文件。

其他的环境变量配置文件

还有一些环境变量配置文件,最常见的就是 ~/bash_history 文件,也就是历史命令保存文件。这个文件已经讲过了,在这里我们只把它归入环境变量配置文件小节而已。

13.Shell登入信息及修改方法

我们在登录 tty1~tty6 这 6 个本地终端时,会有几行的欢迎界面。这些欢迎信息是保存在哪里的?可以修改吗?当然可以修改,这些欢迎信息保存在 /etc/issue 文件中,我们查看一下这个文件:

[root@localhost ~]# cat /etc/issue
CentOS release 6.3 (Final)
Kernel \r on an \m

系统在每次登录时,会依赖这个文件的配置显示欢迎界面。在 /etc/issue 文件中允许使用转义符调用相应信息,其支持的转义符可以通过 man agetty 命令查询,如图所示。

  [root@localhost ~]# cat /etc/issue
在本地终端登录时,因为有 tty1~tty6 这 6 个本地终端(可以通过 Alt+F1 ~ F6 快捷键切换),有时我们会忘记在哪个终端中,所以笔者更习惯加入"\l"选项,例如:

CentOS release 6.3 (Final)
Kernel \r on an \m \l

这样我们在本地登陆时,就可以看到到底是在哪个终端中。

/etc/issue.net

配置 /etc/issue 文件会在本地终端登录时显示欢迎信息,如果远程登录(如 ssh 远程登录,或 Telnet 远程登录)需要显示欢迎信息,则需要配置 /etc/issue.net 文件。使用这个文件时有两点需要注意:

  • 在 /etc/issue 文件中支持的转义符在 /etc/issue.net 文件中不能使用。
  • ssh 远程登录是否显示 /etc/issue.net 文件中的欢迎信息,是由 ssh 的配置文件决定的。


如果我们需要 ssh 远程登录可以査看 /etc/issue.net 文件中的欢迎信息,那么首先需要修改 ssh 的配置文件 /etc/ssh/sshd_config,加入如下内容:

[root@localhost ~]# cat /etc/ssh/sshd_config ...省略部分输出...
# no default banner path
#Banner none
Banner /etc/issue.net
…省略部分输出…
View Code

这样,在 ssh 远程登录时,也可以显示欢迎信息,只是不能再识别"\d"和"\l"等信息了。

/etc/motd

/etc/motd 文件中也是有欢迎信息的,这个文件和 /etc/issue 及 /etc/issue.net 文件的区别是:/etc/issue 及 /etc/issue.net 文件是在用户登录之前显示欢迎信息的;而 /etc/motd 文件是在用户输入用户名和密码,正确登录之后显示欢迎信息的。/etc/motd 文件中的欢迎信息,不论是本地登录,还是远程登录,都可以显示。

大家需要注意,在国外曾经有黑客入侵服务器,因为服务器上显示的欢迎信息是"welcome..."而免于处罚的案例。所以,我们虽然一直是按照"欢迎信息"进行讲解的,但是这里其实应该写入一些"警告信息", 比如禁止非法用户登录之类的信息。

14.Shell自定义快捷键

首先,我们要学会查看 Shell 中已有快捷键的方法,如下所示:

[root@localhost ~]# stty -a
#查询所有的快捷键
speed 38400 baud; rows 21; columns 1.04; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time =0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts -cdtrdsr
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc —ixany -imaxbel
-iutf8
opost -Olcuc -ocrnl onlcr -onocr -Onlret -ofill -ofdel nlO crO tabO bsO vtO ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
View Code

"-a"选项用于査询系统中所有可用的快捷键,可以看到,"Ctrl+C"用于强制中止,"Ctrl+D"用于中止输入。那么,这些快捷键可以更改吗?

当然可以,只需执行以下命令即可:

[root@localhost ~] # stty 关键字 快捷键
例如:
[root01ocalhost ~]# stty intr ^p
#定义Ctrl +P快提键为强制中止,"^"字符只需手工输入即可
[root@localhost ~】# stty -a
speed 38400 baud; rows 21; columns 104; line = 0;
intr = ^P; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
#强制中止的快提键变成了Ctrl+P
View Code

分类:

技术点:

相关文章:

  • 2021-05-25
  • 2021-05-28
猜你喜欢
  • 2021-06-20
  • 2021-11-27
  • 2022-12-23
  • 2021-04-12
  • 2021-11-30
  • 2021-06-25
相关资源
相似解决方案