一、Shell概述

 

 

前言:

计算机只能认识(识别)机器语言(0和1),如(11000000 这种)。但是,我们的程序猿们不能直接去写01这样的代码,所以,要想将程序猿所开发的代码在计算机上运行,就必须找"人"(工具)来翻译成机器语言,这个"人"(工具)就是我们常常所说的**编译器或者解释器**。

 

1. 编程语言分类

 

编译型语言:

​ 程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++

解释型语言

​ 程序不需要编译,程序在运行时由**解释器**翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。比如Python/JavaScript/ Perl /ruby/Shell等都是解释型语言。

 

总结

编译型语言比解释型语言速度较快,但是不如解释型语言跨平台性好。如果做底层开发或者大型应用程序或者操作系开发一般都用编译型语言;如果是一些服务器脚本及一些辅助的接口,对速度要求不高、对各个平台的兼容性有要求的话则一般都用解释型语言。

 

 

什么是Shell

 

解释Shell脚本名词之前,我们先来说下什么是Shell?

Shell是一个命令解释器,它在操作系统的最底层,负责直接与用户对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,输出到屏幕返回给用户。这种对话方式可以是交互的方式(从键盘输入命令,可以立即得到shell的回应),或非交互(脚本)的方式。

命令行解释器:使用户得以与内核进行沟通的“翻译官”

下面图中黄色部分就是命令解释器shell。

 

提示:Shell在英文中的意思是贝壳,从上图我们可以看出,命令解释器shell就像一个贝壳一样包住了系统核心。

 

解释:

用户下达指令,shell把用户的指令翻译成`系统内核`能识别的二进制数,然后传达给系统内核,系统内核会把指令进行查看分析,然后下达“进程调度”和“资源分配”指令给CPU,让CPU去执行,然后由计算机硬件来显示结果。

 

 

2.什么是shell脚本

Linux  Shell脚本 :实现某种功能的,有执行权限的文本文件

内核的作用:资源分配和进程调度

 

命令或语句不在命令行执行,而是通过一个程序文件执行时,该程序就被称为shell脚本或Shell程序,shell程序很类似DOS系统下的批处理程序(*.bat)。用户可以在shell脚本中敲入一系列的命令或命令语句。这些命令、变量和流程控制语句等有机的结合起来就形成了一个功能强大的shell脚本。

 

一句话概括

简单来说就是将需要执行的命令保存到文本中,按照顺序执行。它是解释型的,意味着不需要编译。

准确叙述

若干命令 + 脚本的基本格式 + 脚本特定语法 + 思想= shell脚本

 

 什么时候用到脚本?

重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。

①自动化软件部署 LAMP/LNMP/Tomcat...

②自动化管理 系统初始化脚本、批量更改主机密码、推送公钥...

③自动化分析处理 统计网站访问量

④自动化备份 数据库备份、日志转储...

⑤自动化监控脚本

 

如何学习shell脚本?

 

1.尽可能记忆更多的命令(记忆命令使用功能和场景)

2.掌握脚本的标准的格式(指定魔法字节、使用标准的执行方式运行脚本)

3.必须==熟悉掌握==脚本的基本语法(重点)

 

学习shell脚本的秘诀

多看(看懂)——>模仿(多练)——>多思考(多写)

 

 

 

范例1:清除.var/log下messages日志文件的简单命令脚本

#把所有命令放在一个文件里堆积起来就形成了脚本,下面就是一个最简单的命令堆积形成的shell脚本。

#必须使用root身份来运行这个脚本

#清除日志脚本,版本1

cd /var/log
cat /dev/null > messages
echo "Logs cleaned up."
#提示:/var/log/messages是系统的日志文件

 

看完这个脚本有什么想法?

1)不是root执行不了

2)没有流程控制,就是没有逻辑性

 

 

范例2:包含命令、变量和流程控制语句的清除/var/log下messages日志文件的shell脚本

#!/bin/bash
#清除日志脚本,版本2
LOG_DIR=/var/log
ROOT_UID=0   #$UID=0的时候,用户才具有root用户的权限
#要使用root用户来运行.
if [ "$UID" -ne "$ROOT_UID" ]
then
    echo "Must be root to run this scripts."
    exit 1
fi
cd $LOG_DIR || {
    echo"Cannot change to necessary directory." >&2
    exit 1
}

cat /dev/null > messages && echo "Logs cleaned up."
exit 0 
#退出之前返回0表示成功,返回1表示失败

 

扩展:

清除日志的三种方法:

 

[root@Web ~]# echo >test.log 
[root@Web ~]# >test.log 
[root@Web ~]# cat /dev/null >test.log 

 

应用场景:保留文件,清空内容

 

 

 脚本在执行时会启动一个子shell进程;

1.命令行中启动的脚本会继承当前shell环境变量;

2.系统自动执行的脚本(非命令行启动)就需要自我定义需要各环境变量;

 


 

 

进程和shell的关系

 

 

1.在每个进程看来,当前主机上只存在内核和当前进程,不知道别的进程存在

2.进程是程序的副本,进程是程序执行实例

3.进程可同名,进程号不同就是不同的进程

4.进程是程序的副本,进程是程序执行示例

5.多个用户同时登录shell,每个Shell都会有所不同,彼此之间各不相干

6.允许同一个用户登录多次,每个Shell都会有所不同,彼此之间各不相干

7.同一个用户在不同地方登录的Shell都会不同,都对应不同的Shell进程
8.bash自身是一个外部程序,但启动以后会带有内部命令
9.父Shell的设定对于子Shell是无效的,所之亦然。
10.shell之间可以交互打开

 

 

shell脚本与perl,php,python语言的差别?

  Shell的优势在于处理操作系统底层的业务(大量的命令为它做支撑,例如,grep,sed,awk)。

一键安装,报警脚本,常规的业务应用,shell开发更简单快速。

  php,python优势在于开发运维工具,web界面的管理工具

 

 Shell脚本很擅长处理纯文本类型的数据,而linux中几乎所有的配置文件、日志文件(如nfs,rsync,httpd,nginx,lvs等)都是纯文本类型的文本。因此,如果学好Shell脚本语言,就可以利用它Linux系统中发挥巨大的作用。

 

Linux系统支持的Shell

 

 

[root@MissHou ~]# cat /etc/shells 
/bin/sh            #是bash shell的一个快捷方式
/bin/bash        #bash shell是大多数Linux默认的shell,包含的功能几乎可以涵盖shell所有的功能
/sbin/nologin    #表示非交互,不能登录操作系统
/bin/dash        #小巧,高效,功能相比少一些
/bin/tcsh        #是csh的增强版,完全兼容csh
/bin/csh        #具有C语言风格的一种shell,具有许多特性,但也有一些缺陷
~~~

 

  Shell脚本语言是弱类型语言,较为通用的shell有标准的Bourne shell (sh)和 C shell (csh)。其中 Bourne shell (sh)。其中 Bourne shell (sh)已经被bash shell取代。

 

CentOS Linux系统默认的shell是():

 

[root@Web ~]# echo $SHELL 
/bin/bash
[root@Web ~]# grep root /etc/passwd
[root@Web ~]# grep  root /etc/passwd
root:x:0:0:root:/root:/bin/bash

 

 

 

 Bash的基本特性

 

 

Tab键补全、快捷键
命令历史
命令别名
标准输入输出
重定向
管道操作

 

 

 

Tab键补全

 

 

命令补全    搜索PATH环境变量所指定的每个路径下以我们给出的字符串 开头的可执行文件,如果多于一个,两次tab,可以给出列表;否则将直接补全;
路径补全    搜索我们给出的起始路径下的每个文件名,并试图补全

 

 

快捷键

 

 

 

快捷键    注释
Ctrl+a    跳到命令行首
Ctrl+e    跳到命令行尾
Ctrl+u    删除光标至命令行首的内容
Ctrl+k    删除光标至命令行尾的内容
Ctrl+l    清屏


^c               终止前台运行的程序
^z                  将前台运行的程序挂起到后台
^d               退出 等价exit
^l               清屏
^a |home      光标移到命令行的最前端
^e |end      光标移到命令行的后端
^u               删除光标前所有字符
^k               删除光标后所有字符
^r                 搜索历史命令

 

常用的通配符(重点)

 

*:    匹配0或多个任意字符
?:    匹配任意单个字符
[list]:    匹配[list]中的任意单个字符
[!list]: 匹配除list中的任意单个字符
{string1,string2,...}:匹配string1,string2或更多字符串

 举例:

touch file{1..3}
touch file{1..13}.jpg
# ls file*
# ls *.jpg
# ll file?
# ll file?.jpg
# ll file??.jpg
# ll file1?.jpg
# ll file?.jpg
# ll file[1023].jpg
# ll file[0-13].jpg
# ll file1[0-9].jpg
# ll file[0-9].jpg
# ll file?[1-13].jpg
# ll file[1,2,3,10,11,12].jpg
# ll file1{11,10,1,2,3}.jpg
# ll file{1..10}.jpg
# ll file{1...10}.jpg
~~~

 

bash中的引号(重点)

 

 双引号""   :会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
 单引号''     :会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
反撇号``  :反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用

例子:

[root@server dir1]# echo "$(hostname)"
server
[root@server dir1]# echo '$(hostname)'
$(hostname)
[root@server dir1]# echo "hello world"
hello world
[root@server dir1]# echo 'hello world'
hello world

[root@server dir1]# echo $(date +%F)
2018-11-22
[root@server dir1]# echo `echo $(date +%F)`
2018-11-22
[root@server dir1]# echo `date +%F`
2018-11-22
[root@server dir1]# echo `echo `date +%F``
date +%F
[root@server dir1]# echo $(echo `date +%F`)
2018-11-22

 

 

命令历史

 

默认记录1000条最近执行过的命令

存放位置:~/.bash_history

 

环境变量    注释
PATH      命令搜索路径
HISTSIZE    命令历史缓冲区大小

 

 

命令历史:history

 

参数选项    注释
-c    清空命令历史
-d OFFSET [n]    删除指定位置的命令
-w    保存命令历史至历史文件中

 

命令历史的使用技巧:

 

 

参数选项    注释
!n       执行命令历史中的第n条命令
!-n      执行命令历史中的倒数第n条命令;
!!       执行上一条命令;
!string    执行命令历史中最近一个以指定字符串开头的命令
!$       引用前一个命令的最后一个参数
Esc, .    
Alt+.    

 

 

命令别名

 

语法:

alias CMDALIAS='COMMAND [options] [arguments]'

 

有效范围

 

 

在shell中定义的别名仅在当前shell生命周期中有效
别名的有效范围 仅为当前shell进程

 

相关操作

 

 

相关操作                注释
aliase              列出已定义的命令别名
unaliase 别名          删除指定的命令别名
unaliase -a           清空所有别名
alias 别名='实际命令'      定义新的命令别名

 

 

 

 

如何让定义的别名命令生效?

 

 

vim  ~/.bashrc    只对某个用户生效
vim /etc/bashrc    对所有用户生效

 

 

 

标准输入输出

 

 

1.标准输入:从该设备接收用户输入的数据

 

2.标准输出:从该设备向用户输出的数据

 

3.标准错误:通过该设备报告执行中的出错信息

 

 

 

类型      设备文件      文件描述号    默认设备
标准输入    /dev/stdin      0        键盘
标准输出    /dev/stdout     1        显示器
标准错误    /dev/stderr     2        显示器

 

 

 

重定向

 

 

 

重定向:改变标准输入/输出/错误输出的方向

 

 

 

set -C    禁止对已经存在文件使用覆盖重定向
        强制覆盖输出,则使用 >|
set +C    关闭上述功能

 

 

 

 

类型

 

类型                    操作符          用途
重定向输入                  <        将文本输入来源由键盘改为指定的文件
重定向输出                  >        将命令行的正常执行输出保存到文件,而不是直接显示在屏幕上
                       >>        与>类似,但操作是追加而不是覆盖
重定向错误                  2>       将命令行的执行错误信息保存到文件,而不是直接显示在屏幕上
                        2>>       与2>类似,但操作是追加而不是覆盖
混合重定向(重定向正确和错误输出信息)      &>      相当于>和2>,覆盖到同一个文件
                       &>>      相当于>和2>>,追加到同一个文件

 

 

 

 

管道

 

 

管道:前一个命令的输出,作为后一个命令的输入

 

管道操作符”|“    
cmd1 | cmd2 [...|cmdn]    将cmd1的输出结果,作为cmd2的输入

 

 

例如:

#pgrep -l "." | wc -l————统计运行的进程数
#ifconfig | grep  "HWaddr"————查看本机的MAC地址信息

 

 

 

命令的逻辑分隔

 

 

;     命令1 ; 命令2  多个命令顺序执行,命令1和命令2之间没有逻辑联系
&&     命令1 && 命令2     命令1执行正确命令2才执行,命令1执行不正确,则命令2不执行

||  命令1 || 命令2     命令1执行不正确命令2才执行,命令1执行正确,则命令2不执行

 

 

 

shell脚本的建立和执行

 

 shell脚本(bash shell程序)通常是在编辑器(如vi/vim)中编写,由Unix/Linux命令、bash shell命令、程序结构控制语句和注释等内容组成。

脚本开头(第一行)

  一个规范的shell脚本第一行会指出由哪个程序(解释器)来执行脚本中的内容,在linux bash编程中一般为:

#!/bin/bash
或
#!/bin/sh

 

“#!”又称为幻数,在执行bash脚本的时候,内核会根据它来确定该用哪个程序来解释脚本中的内容。这一行必须在脚本顶端的第一行,如果不是则为注释。

#!/bin/bash 表示以下内容使用bash解释器解析

注意: 如果直接将解释器路径写死在脚本里,可能在某些系统就会存在找不到解释器的兼容性问题,所以可以使用:#!/bin/env 解释器
#!/bin/env bash

 

 

 

sh和bash的区别:

 

[root@Web ~]# ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 9月  25 2014 /bin/sh -> bash
[root@Web ~]# ll /bin/sh 
lrwxrwxrwx. 1 root root 4 9月  25 2014 /bin/sh -> bash

 

提示:sh为bash的软链接,这里推荐用标准写法#!/bin/bash

 

下面是linux常用脚本语言开头的编码写法,不同语言脚本的开头一般都要加上如相应语言的开头标识内容

#!/bin/sh
#!/bin/bash
#!/usr/bin/awk
#!/bin/sed
#!/usr/bin/tcl
#!/usr/bin/expect
#!/usr/bin/perl
#!/usr/bin/env python

 

 Centos和RedHat Linux下默认的Shell均为bash。因此,在写shell脚本的时候,我们的脚本的开头也可以不加#!/bin/bash。但如果当前的shell非你默认的shell时,比如tcsh,那么就必须要写#!了。否则脚本文件就只能执行命令的集合,不能够使用shell内建的指令了,建议读者养成习惯,不管什么脚本最好都加上开头语言标识。

如果脚本的开头不指定解释器,那么,就要用对应的解释器来执行脚本。例如:

bash test.sh
python test.py

 

 

 

脚本注释

shell脚本中,跟在(#)井号后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当作程序执行,仅仅是给人看的,系统解释器看不到的,注释可自成一行,也可以跟在脚本命令后面与命令在同一行。开发脚步时,如果没有注释,其他人就很难理解脚本究竟在做什么,时间长了自己也会忘记。因此,我们要尽量养成对所做的工作(脚本等)书写注释的习惯,不光是方便别人,也方便自己,否则,写完一个脚本后也许几天后就记不起脚本的用途了,再重新阅读也会浪费很多宝贵时间。对于团队的协作也不利。

 

# 以下内容是对脚本的基本信息的描述
# Name: 名字
# Desc:描述describe
# Path:存放路径
# Usage:用法
# Update:更新时间

 

Shell脚本的执行

 

 

 

shell脚本以非交互式的方式运行时,它会先查找环境变量ENV,该变量指定了一个环境文件(通常是.bashrc),然后从该环境变量文件开始执行,当读取了ENV文件后,SHELL才开始执行shell脚本中的内容。

 

Shell脚本的执行通常可以采用一下三种方式:

bash script-name 或sh script-name(推荐使用)
path/script-name或./script-name(当前路径下执行脚本)
source script-name或. script-name    #注意”.”点号;不需x权限 

 

执行说明:

1.第一种方法是当脚本文件本身没有可执行权限(即文件x位为-号)时常使用的方法,这里推荐使用bash执行,或者文件开头没有指定解释器(推荐的方法)

2.第二种方法需要先将脚本文件的权限改成可执行文件(即文件加x位),具体方法:chmod u+x script-name或chmod 755 script-name。

然后通过脚本路径就可以直接执行脚本了。

在生产环境中,不少读者在写完shell脚本后,由于疏忽忘记给予该脚本文件执行权限,就直接应用了,结果导致脚本没有按自己的意愿手动或定时执行。

避免的方法是用第一种方法替代第二种。

3.第三种方法是使用source或者”.”点号读入或加载指定的shell脚本文件(如san.sh),然后依次执行指定shell脚本文件san.sh中的所有语句。这些语句将作为当前父shell脚本father.sh进程的一部分运行。因此,使用source或者”.”点号可以将san.sh自身脚本中的变量的值或函数等的返回值传递到当前的父shell脚本father.sh中使用。这是第三种方法和前两种的最大区别。也是值得读者注意的地方。

Source或者'.'点号命令的功能是在当前shell中执行source或者'.'点号加载并执行的相关脚本文件中的命令及语句,而不是产生一个子shell来执行命令文件中的命令

 

 

下面我们举例说明

[zgy@Web ~]$ cat >test.sh                   #编辑test.sh脚本文件

echo 'I am zgy' echo 'I am zgy'  

 

输入"echo 'I am oldboy'"内容后按回车,然后在按ctrl+d组合键即可结束编辑。此操作作为特殊编辑方法,作为cat用法的扩展提及(在使用中去记忆是个好习惯)。

 

[zgy@Web ~]$ cat test.sh  

echo 'I am zgy'   

[zgy@Web ~]$ sh test.sh         #使用第一种方式的sh命令执行test.sh脚本文件

I am zgy

[zgy@Web ~]$ bash test.sh      #使用第一种方式的bash命令执行test.sh脚本命令

I am zgy

 

我们使用第一种方法,发现均可以执行并得到预期的结果。

zgy@Web ~]$ ls -l test.sh
-rw-rw-r--. 1 zgy zgy 22 4月  11 12:45 test.sh

[zgy@Web ~]$ ./test.sh         #使用第二种方式"./"在当前目录下执行test.sh脚本文件,可以发现,这个地方无法自动补全。
-bash: ./test.sh: 权限不够    #提示:此处没有可执行权限

 

但是可以用source或者"."点号执行。

[zgy@Web ~]$ . test.sh
I am zgy
[zgy@Web ~]$ source test.sh
I am zgy

 

提示:"."点号和source命令的功能相同,都是读入脚本并可以执行脚本

 

test.sh加执行权限

[zgy@Web ~]$ chmod u+x test.sh 
[zgy@Web ~]$ ./test.sh 
I am zgy

 

可以看到,给test.sh加完可执行权限就可以执行了。

 

现在测试第三种source或者"."点号的特殊的传递变量值到当前shell的例子

 

[zgy@Web ~]$ echo 'userdir=`pwd`'>testsource.sh  #行的内容通常用echo很方便。
[zgy@Web ~]$ cat testsource.sh
userdir=`pwd`
[zgy@Web ~]$ sh testsource.sh
[zgy@Web ~]$ echo $userdir

#此处为空,并没有出现当前路径输出,这是为什么呢?

 

根据上面例子,可以发现,通过sh或bash命令执行过的脚本,脚本结束后在当前shell窗口查看userdir变量的值,发现变量值是空的。现在以同样的步骤改用source执行,然后再看看userdir变量的值

 

[zgy@Web ~]$ source testsource.sh 
[zgy@Web ~]$ echo $userdir
/home/zgy

 

再看看系统Nfs服务的脚本如何使用”.”号的。

# Source function library.
. /etc/rc.d/init.d/functions

 

提示:操作系统及服务自带的脚本是学习的标杆和参考(虽然有时感觉不是很规范)

 

结论:

通过source或”.”点号加载执行过的脚本,在脚本结束后脚本中的变量值(包括函数)在当前Shell中依然存在,而是sh和bash则不行。

因此,在做Shell脚本开发时,如果脚本中有需求引用其他脚本的内容或者配置文件时,最好用”.”点号或source在脚本开头加载该脚本或配置文件,

然后再下面的内容用可以调用source加载的脚本及文件中的变量及函数等内容

 

 

某互联网公司Linux运维职位实际面试笔试

1、已知如下命令返回结果,请问echo $user的返回的结果为()
[zgy@Web ~]$ cat test.sh
user=`whoami`
[zgy@Web ~]$ sh test.sh
[zgy@Web ~]$ echo $user
问:执行echo $user命令的结果是什么?
1)当前用户
2)zgy
3)空(无内容输出)   #这个是正确答案

 

 

Shell脚本开发基本规范及习惯

 

1)开头指定脚本解释器

#!/bin/sh或#!/bin/bash

 

2)开头加版权版本等信息

 

#Date: 16:29 2015-4-11

#Author Created by zgy

#Mail 1092327070@qq.com

#Function:  This scripts function is...

#Version 1.1

 

 

3)脚本中不用中文注释

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

 

4)脚本以.sh为扩展名

 

例:script-name.sh

5)代码书写优秀习惯

1.成对内容的一次写出来,防止遗漏。如:

[]、{}、''、``、""

 

 

2.[]中括号两段要有空格,书写时即可留出空格[  ],然后在退格书写内容。

3.流程控制语句一次书写成,再添加内容,如:

 if语句格式一次完成:

if  条件内容
then  
内容
fi

 

for语句格式一次完成:

for  
do
   内容
done

提示:while和util,case等语句也是一样

 

6)通过缩进让代码易读

 

if 条件内容
then
    内容
fi

 

提示:好的习惯可以避免很多不必要的麻烦,提升很多的工作效率。

 

posted @ 2018-04-24 21:13  钟桂耀  阅读(680)  评论(0编辑  收藏  举报