Shell编程入门基础上

前言

为什么学 Shell

Shell 脚本语言是实现 Linux/UNIX 系统管理及自动化运维所必备的重要工具, Linux/UNIX 系统的底层及基础应用软件的核心大都涉及 Shell 脚本的内容。每一个合格 的Linux 系统管理员或运维工程师,都需要能够熟练地编写 Shell 脚本语言,并能够阅读系统及各类软件附带的 Shell 脚本内容。只有这样才能提升运维人员的工作效率,适 应曰益复杂的工作环境,减少不必要的重复工作,从而为个人的职场发展奠定较好的基础

什么是 shell

Shell 是一个命令解释器,它在操作系统的最外层,负责直接与用户对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,输出屏幕返回给用户。

Shell编程入门基础上

这种对话方式可以是:

  • 交互的方式:从键盘输入命令,通过 /bin/bash 的解析,可以立即得到 Shell 的回应
[root@mico ~]# echo "hello"
hello
[root@mico ~]# pwd
/root
[root@mico ~]#
  • 非交互的方式: 脚本

什么是Shell脚本

  • Linux命令、变量和流程控制语句等有机的结合起来并以程序文件形式执行,该程序称为shell脚本。
  • Shell脚本语言是弱类型语言(无须定义变量的类型即可使用), shell脚本擅长处理纯文本类型的数据,而linux中,几乎所有的配置文件,日志,都是纯文本类型文件。

脚本语言的种类

编译型语言

    定义:指用专用的编译器,针对特定的操作平台(操作系统)将某种高级语言源代码一次性翻译成可被硬件平台直接运行的二进制机器码(具有操作数,指令、及相应的格式),这个过程叫做编译(./configure  make makeinstall );编译好的可执行性文件(.exe),可在相对应的平台上运行(移植性差,但运行效率高)。典型的编译型语言有, C语言、C++等。另外,Java语言是一门很特殊的语言,Java程序需要进行编译步骤,但并不会生成特定平台的二进制机器码,它编译后生成的是一种与平台无关的字节码文件(*.class)(移植性好的原因),这种字节码自然不能被平台直接执行,运行时需要由解释器解释成相应平台的二进制机器码文件;大多数人认为Java是一种编译型语言,但我们说Java即是编译型语言,也是解释型语言也并没有错。

解释型语言

    定义:指用专门解释器对源程序逐行解释成特定平台的机器码并立即执行的语言;相当于把编译型语言的编译链接过程混到一起同时完成的。解释型语言执行效率较低,且不能脱离解释器运行,但它的跨平台型比较容易,只需提供特定解释器即可。常见的解释型语言有, Python(同时是脚本语言)与Ruby等。

脚本语言

    定义:为了缩短传统的编写-编译-链接-运行(edit-compile-link-run)过程而创建的计算机编程语言。
特点:程序代码即是最终的执行文件,只是这个过程需要解释器的参与,所以说脚本语言与解释型语言有很大的联系。脚本语言通常是被解释执行的,而且程序是文本文件。

典型的脚本语言有,JavaScript,Python,shell等。

其他常用的脚本语句种类

  • PHP是网页程序,也是脚本语言。是一款更专注于 web 页面开发(前端展示)的脚本语言,例如:Dedecms,discuz。PHP 程序也可以处理系统日志,配置文件等,php 也可以调用系统命令。
  • Perl 脚本语言。比 shell 脚本强大很多,语法灵活、复杂,实现方式很多,不易读,团队协作困难,但仍不失为很好的脚本语言,存世大量的程序软件。MHA 高可用 Perl 写的
  • Python,不但可以做脚本程序开发,也可以实现 web 程序以及软件的开发。近两年越来越多的公司都会要求会 Python。

Shell脚本与 php/perl/python 语言的区别和优势?

shell 脚本的优势在于处理操作系统底层的业务 (linux 系统内部的应用都是 shell 脚本完成)因为有大量的 linux 系统命令为它做支撑。2000 多个命令都是 shell 脚本编程的有力支撑,特别是grep、awk、sed 等。例如:一键软件安装、优化、监控报警脚本,常规的业务应用,shell 开发更简单快速,符合运维的简单、易用、高效原则。PHP、Python 优势在于开发运维工具以及 web 界面的管理工具,web 业务的开发等。处理一键软件安装、优化,报警脚本。常规业务的应用等 php/python 也是能够做到的。但是开发效率和复杂比用 shell 就差很多了。

系统中的shell

查看系统环境

[root@mico ~]# cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)
[root@mico ~]# uname -r
3.10.0-862.11.6.el7.x86_64
[root@mico ~]# getenforce
Disabled
[root@mico ~]# systemctl status firewalld.service
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Docs: man:firewalld(1)
[root@mico ~]#

查看系统中的命解释器

[root@mico ~]# cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/tcsh
/bin/csh
[root@mico ~]#

常用操作系统的默认shell

  • Linux是Bourne Again shell(bash)
  • Solaris和FreeBSD缺省的是Bourne shell(sh)
  • AIX下是Korn Shell(ksh)
  • HP-UX缺省的是POSIX shell(sh)
[root@mico ~]# echo $SHELL
/bin/bash

查看系统的 Bash 版本

[root@VM_42_34_centos /]# head -1 /etc/init.d/network
#! /bin/bash
[root@VM_42_34_centos /]# head -1 /etc/init.d/netconsole
#!/bin/bash

bash版本

[root@mico ~]# bash -version
GNU bash, 版本 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
许可证 GPLv3+: GNU GPL 许可证版本3或者更高 <http://gnu.org/licenses/gpl.html> 这是*软件,您可以*地更改和重新发布。
在法律允许的范围内没有担保.

bash 破壳漏洞

使用 命令 env x='() { :;}; echo be careful' bash -c "echo this is a test"

如果返回结果为一行,则为正常,

 [root@mico ~]# env x='() { :;}; echo be careful' bash -c "echo this is a test"
this is a test
#解决办法 升级当前的bash版本
yum install update bash

bash 和 sh 的区别

早期的bash和sh稍有不同,还包含了csh和ksh的特色,但大多数脚本都可以不加修改地在sh上运行

[root@VM_42_34_centos ~]# ll /bin/bash
-rwxr-xr-x 1 root root 964544 4月 11 2018 /bin/bash
[root@VM_42_34_centos ~]# ll /bin/sh
lrwxrwxrwx 1 root root 4 8月 10 2018 /bin/sh -> bash

说明:sh为bash的软链接,一般情况下,脚本开头使用“#!/bin/bash”和"#!/bin/sh"是没有区别的,但更规范的写法是在脚本开头使用“#!/bin/bash”

下面的Shell脚本是系统自带的软件启动脚本的开头部分

sh与bash 的关系

[root@mico ~]# ll /bin/sh
lrwxrwxrwx 1 root root 4 8月 10 2018 /bin/sh -> bash

/bin与 /user/bin 的关系

[root@mico ~]# ll /bin -d
lrwxrwxrwx 1 root root 7 8月 10 2018 /bin -> usr/bin

脚本书写规范

脚本统一存放目录

[root@mico ~]# mkdir -p /server/scripts/
[root@mico ~]# cd /server/scripts/

选择解释器

脚本第一行指出由哪个程序(解释器)来执行脚本中的内容

#!/bin/bash#!/bin/sh (255个字符以内)

注意格式 ↓

其中开头的"#!"字符又称为幻数,在执行bash脚本的时候,内核会根据"#!"后的解释器来确定该用那个程序解释这个脚本中的内容。

[root@mico scripts]# head -1 /etc/init.d/*
==> /etc/init.d/functions <==
# -*-Shell-script-*- ==> /etc/init.d/netconsole <==
#!/bin/bash ==> /etc/init.d/network <==
#! /bin/bash ==> /etc/init.d/README <==
You are looking for the traditional init scripts in /etc/rc.d/init.d,

编辑脚本使用vim

使用 .vimrc 文件,将下面的信息添加到 .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, "# Author: mico")
call setline(6, "# Created Time : ".strftime("%F %T"))
call setline(7, "# Description:")
call setline(8, "##############################################################")
call setline(9, "")
endif
endfunc

使用后的效果

#!/bin/bash
##############################################################
# File Name: scripts_tmp.sh
# Version: V1.0
# Author: mico
# Created Time : 2019-07-09 10:12:27
# Description:
##############################################################

在Shell脚本中,跟在#后面的内容表示注释。注释部分不会被执行,仅给人看。注释可以自成一行,也可以跟在命令后面,与命令同行。要养成写注释的习惯,方便自己与他人。

最好不用中文注释,因为在不同字符集的系统会出现乱码。(字符集为zh_CN.UTF-8,为中文)。

文件名规范

名字要有意义,并且结尾以 .sh 结束(并不是必须)

开发的规范和习惯小结

  • 放在统一的目录
  • 脚本以 .sh 为扩展名
  • 开头指定脚本解释器。
  • 开头加版本版权等信息,可配置 ~/.vimrc 文件自动添加。
  • 脚本不要用中文注释,尽量用英文注释。
  • 代码书写优秀习惯
    • 成对的内容一次性写出来,防止遗漏,如[ ]' '" "
    • [ ] 两端要有空格,先输入[ ],退格,输入 2 个空格,再退格写。
    • 流程控制语句一次书写完,再添加内容。(if 条件 ; then 内容;fi)
    • 通过缩进让代码易读。
    • 脚本中的引号都是英文状态下的引号,其他字符也是英文状态。

shell脚本的执行

当 Shell 脚本运行时,会先查找系统环境变量 ENV,该变量指定了环境文件(加载顺序通常是 /etc/profile~/.bash_profile~/.bashrc/etc/bashrc ),在加载了上述变量文件后,Shell就开始执行脚本中的内容。

Shell编程入门基础上

Shell 脚本是从上至下、从左至右依次执行每一行的命令及语句的。即执行完了一个命令后再执行下一个,如果在 Shell 脚本中(即脚本嵌套)

时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后序的命令及语句。

通常情况下,在执行 Shell 脚本时,会向系统内核请求启动一个新进程,以便在该进程或只能怪执行脚本的命令以及子Shell 脚本。

执行脚本的办法

执行脚本的四种方法

(1)sh/bash scripts.sh

当脚本本身没有可执行权限时使用的方法,或脚本文件开头没有指定解释器时需要使用分方法。

(2)首先chown +x ,然后./scripts.sh .path/scripts.sh

在当前路径下执行脚本(脚本需要有执行权限),通过绝对路径或相对路径直接执行脚本

(3)source scripts.sh. (空格) scripts.sh

这种方法通常是使用 source. 读入或加载指定的 shell 脚本文件(不需要执行权限),然后依次执行指定 shell 脚本中的所有语句。这些语句将在当前 shell 脚本的进程中运行(其他几种模式都会启动新的进程执行子脚本)。source. 命令的功能是在当前 Shell 中执行 source. 加载并执行的相关脚本文件中的命令及语句,而不是产生一个子 Shell 来执行文件中的命令。

说明.source 命令的功能相同,都是读入脚本并执行脚本中的命令。

(4)sh < script-name 或 cat scripts-name|sh 效率低

同样适用于bash,不过这种用法不常见。

source 与 . (点) 的作用

soucre命令

[root@mico ~]# help source  |head -2
source: source 文件名 [参数]
在当前 shell 中执行一个文件中的命令。
. (点)
[root@mico ~]# help . |head -2
.: . 文件名 [参数]
在当前 shell 中执行一个文件中的命令。

sh于source的区别

对比:用 source 执行脚本文件,执行过程不另开进程,脚本文件中设定的变量在当前shell中可以看到;

用 sh/bash 执行脚本文件,是在当前进程另开子进程来执行脚本命令,脚本文件中设定的变量在当前shell中不能看到。

例如

[root@VM_42_34_centos trsky]# echo 'userdir=`pwd`' >> testsource.sh
[root@VM_42_34_centos trsky]# cat testsource.sh
userdir=`pwd`
[root@VM_42_34_centos trsky]# sh testsource.sh
[root@VM_42_34_centos trsky]# echo $userdir [root@VM_42_34_centos trsky]# source testsource.sh
[root@VM_42_34_centos trsky]# echo $userdir
/home/trsky
[root@VM_42_34_centos trsky]#

由案例可知,使用 source. 执行的脚本中的变量值会传递到当前的 Shell 下,因为使用 source. 执行脚本不会开启新的进程,而是在统一进程下完成脚本中所有指令的执行。

Shell编程入门基础上

[root@mico scripts]# sh  mico_test.sh
Hello World!
[root@mico ~]# echo $Mico
Mico # sh 新建一个Shell窗口(新建一个进程)执行一个文件中的命令。 [root@mico scripts]# source mico_test.sh
Hello World!
[root@mico scripts]# echo $mico
Hello World!

面试题:

问 sh test.sh 后 echo $user 返回的结果__空_ ?

[root@oldboy scripts]# cat test.sh
#!/bin/bash
user=`whoami`

Shell的变量

Shell变量的特性

Shell为弱类型语言,所以在默认情况下,在Bash Shell中是不会区分变量类型的。但Shell可以通 declare 显示定义变量的类型。

什么是变量

变量可以分为两类:环境变量(全局变量)和普通变量(局部变量)

  • 环境变量也可称为全局变量,可以在创建他们的Shell及其派生出来的任意子进程shell中使用,环境变量又可分为自定义环境变量和Bash内置的环境变量
  • 普通变量也可称为局部变量,只能在创建他们的Shell函数或Shell脚本中使用。普通变量一般是由开发者用户开发脚本程序时创建的。
  • 特殊变量

Shell中定义变量名以及为变量内容赋值要求

变量名一般由字母、数字、下划线组成,可以由字母或下划线开头

把一个命令的结果作为变量的内容赋值的方法

  • 变量名=ls
  • 变量名=$(ls)

环境变量

使用 env/declare/set/export -p 命令查看系统中的环境变量,这三个命令的的输出方式稍有不同。

[root@mico scripts]# env
XDG_SESSION_ID=1
HOSTNAME=mico
TERM=linux
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=10.0.0.1 5537 22
SSH_TTY=/dev/pts/0
USER=root
~~~

输出一个系统中的环境变量

[root@mico ~]# echo $LANG
zh_CN.UTF-8

自定义环境变量

设置环境变量

exportdeclare

设置环境变量的3种方法

  • export 变量名=value
  • 变量名=value; export 变量名
  • declare -x 变量名=value

自定义全局变量的实例:

[root@VM_42_34_centos ~]# cat /etc/profile|grep Mico
export Mico='mico'
[root@VM_42_34_centos ~]# source /etc/profile
[root@VM_42_34_centos ~]# echo $MIco [root@VM_42_34_centos ~]# echo $Mico
mico
[root@VM_42_34_centos ~]# env|grep Mico
Mico=mico
[root@VM_42_34_centos ~]#

让环境变量永久生效的常用设置文件

  • 用户环境变量配置
# 用户的环境变量设置,比较常见的是用户家目录下的.bashrc 和 .bash_profile
[root@mico ~]# ls /root/.bashrc
/root/.bashrc
[root@mico ~]# ls /root/.bash_profile
/root/.bash_profile
[root@mico ~]#
  • 全局环境变量的配置

    常见的全局环境变量的配置文件如下:
/etc/profile
/etc/bashrc
/etc/profile.d/

如果要在登陆后初始化或显示加载内容,则将脚本文件放在/etc/profile.d/下即可(无需执行权限)

设置登录提示的两种方式

  • /etc/motd里增加提示的字符串
[root@mico ~]# vim /etc/motd
[root@mico ~]# cat /etc/motd
Welcome to Linux.
  • /etc/profile.d/目录下添加脚本
[root@mico profile.d]# cat /etc/profile.d/mico.sh
#!/bin/bash
##############################################################
# File Name: /etc/profile.d/mico.sh
# Version: V1.0
# Author: mico
# Created Time : 2019-07-01 10:00:06
# Description:
##############################################################
echo "Hello, you are studing Linux"

登录后出现

Connection established.
To escape to local shell, press 'Ctrl+Alt+]'. Welcome to Linux.
Hello, you are studing Linux
[root@mico ~]#

显示与取消环境变量

  • 通过 echo 或 prinf 打印环境变量
#常见的系统环境变量
$HOME:用户登录时进入的目录
$UID:当前用户的UID(用户标识),相当于 id-u
$PWD:当前工作目录的绝对路径名
$SHELL:当前Shell
$USER:当前yonghu
...
打印或删除环境变量
#打印环境
printf "$Home\n"
echo $PWD
  • 使用unset取消环境变量
#删除环境变量
unset PWD

普通变量

本地变量在用户当前的Shell生存期的脚本中使用。例如,本地变量OLDBOY取值为bingbing,这个值在用户当前Shell生存期中有意义。如果在Shell中启动另一个进程或退出,本地变量值将无效

普通变量的定义

变量名=value    //解释变量名
变量名='value' //不解释变量名
变量名="value" //解释变量名

定义普通变量实践

[root@mico ~]# a=1
[root@mico ~]# b='2'
[root@mico ~]# c="3"
[root@mico ~]# echo "$a"
1
[root@mico ~]# echo "$b"
2
[root@mico ~]# echo "${c}"

提示:$变量名表示输出变量,可以用$c和${c}两种用法

【小结】连续普通字符串内容赋值给变量,不管用什么引号或者不用引号,它的内容是什么,打印变量就输出什么

export命令

[root@mico ~]# help export
export: export [-fn] [名称[=值] ...] 或 export -p
为 shell 变量设定导出属性。

标记每个 NAME 名称为自动导出到后续命令执行的环境。如果提供了 VALUE

则导出前将 VALUE 作为赋值。

export命令的说明

  • 当前shell窗口及子shell窗口生效
  • 在新开的shell窗口不会生效,生效需要写入配置文件

定义变量

[root@mico scripts]# CSLN=mico
[root@mico scripts]# export CSLN1=1
# 当前窗口查看
[root@mico scripts]# echo $CSLN
mico
[root@mico scripts]# echo $CSLN1
1
# 编写测试脚本
[root@mico scripts]# vim quanju.sh
#!/bin/bash
echo $CSLN
echo $CSLN1
# 使用sh执行
[root@mico scripts]# sh quanju.sh 1
# 使用source 执行
[root@mico scripts]# source quanju.sh
mico
1

环境变量相关配置文件

  • /etc/proflie
  • /etc/bashrc
  • ~/.bashrc
  • ~/.bash_profile
  • /etc/proflie.d/ # 目录

四文件读取顺序(CentOS6和7都一样)

① /etc/profile

② ~/.bash_profile

③ ~/.bashrc

④ /etc/bashrc

Shell编程入门基础上

系统运行Shell的三种方式

1) 通过系统用户登陆后默认运行的shell

2) 非登录交互式运行Shell

3) 执行脚本运行非交互式Shell

文件读取过程示意图

验证四文件读取顺序的方法

sed -i '1a echo "$(date +%T-%s) /etc/profile1" >>/tmp/mico' /etc/profile
sed -i '$a echo "$(date +%T-%s) /etc/profile2" >>/tmp/mico' /etc/profile
sed -i '1a echo "$(date +%T-%s) /etc/bashrc1" >>/tmp/mico' /etc/bashrc
sed -i '$a echo "$(date +%T-%s) /etc/bashrc2" >>/tmp/mico' /etc/bashrc
sed -i '1a echo "$(date +%T-%s) ~/.bashrc1" >>/tmp/mico' ~/.bashrc
sed -i '$a echo "$(date +%T-%s) ~/.bashrc2" >>/tmp/mico' ~/.bashrc
sed -i '1a echo "$(date +%T-%s) ~/.bash_profile1" >>/tmp/mico' ~/.bash_profile
sed -i '$a echo "$(date +%T-%s) ~/.bash_profile2" >>/tmp/mico' ~/.bash_profile

环境变量的知识小结

  • 变量名通常要大写。
  • 变量可以在自身的Shell及子Shell中使用。
  • 常用export来定义环境变量。
  • 执行env默认可以显示所有的环境变量名称及对应的值。
  • 输出时用“$变量名”,取消时用“unset变量名”。
  • 书写crond定时任务时要注意,脚本要用到的环境变量最好先在所执行的Shell脚本中重新定义。
  • 如果希望环境变量永久生效,则可以将其放在用户环境变量文件或全局环境变量文件里。

环境变量

环境变量一般是指用export内置命令导出的变量,用户定义shell的运行环境。保证shell命令的正确执行。Shell通过环境变量来确定登录用户名、命令路径、终端类型、登录目录等。如果需要永久保存环境变量,可在用户家目录下的.bash_profile或.bashrc(非用户登录模式特有,例如远程登录SSH)文件中,或者全局配置/etc/bashrc(非用户登录模式特有,如远程SSH)或/etc/profile文件中定义。在将环境变量放入上述的文件之后,每次用户登录时这些变量将被初始化。

查看设置的变量:set、env、declare

  • set:输出所有的变量,包括全局变量和局部变量。set -o 显示bash Shell的所有参数配置信息
  • env:只显示全局变量
  • declare:输出所有的变量、函数、整数和已经导出的变量

变量中引号的使用

  • 只有在变量的值中有空格的时候,会使用引号。
  • 单引号与双引号的区别在于,是否能够解析特殊符号。
[root@mico ~]# name=vicodona
[root@mico ~]# name2='mico'
[root@mico ~]# name3="you are my heart"
[root@mico ~]# echo $name
vicodona
[root@mico ~]# echo $name2
mico
[root@mico ~]# echo $name3
you are my heart
[root@mico ~]# name4='mi co'
[root@mico ~]# echo $name4
mi co
[root@mico ~]# name5="mi co"
[root@mico ~]# echo $name5
mi co
[root@mico ~]# name6="mico $PWD"
[root@mico ~]# echo $name6
mico /root
[root@mico ~]# name6='mi co $PWD'
[root@mico ~]# echo $name6
mi co $PWD
[root@mico ~]# name7="mico $PWD"
[root@mico ~]# echo $name7
mico /root

普通变量的要求

  • 内容是纯数字、简单的连续字符(内容中不带任何空格)时,定义时可以不加任何引号,例如:
a.MicoAge=22
b.NETWORKING=yes
  • 没有特殊情况时,字符串一律用双引号定义赋值,特别是多个字符串中间有空格时,例如:
a.NFSD_MODULE="no load"
b.MyName="Oldboy is a handsome boy."
  • 当变量里的内容需要原样输出时,要用单引号(M),这样的需求极少,例如:
a.OLDBOY_NAME='OLDBOY'

变量使用反引号赋值

[root@mico scripts]# time=`date`
[root@mico scripts]# echo $time
2017年 12月 05日 星期二 09:02:06 CST [root@mico scripts]# file=`ls`
[root@mico scripts]# echo $file
mico_test.sh panduan.sh quanju.sh yhk.sh

使用${}

打印变量的时候防止出现“金庸新著”的问题

[root@mico ~]# time=`date`
[root@mico ~]# echo $time
2019年 07月 18日 星期四 17:47:23 CST
[root@mico ~]# echo ${time}_day
2019年 07月 18日 星期四 17:47:23 CST_day
[root@mico ~]# echo $time-day
2019年 07月 18日 星期四 17:47:23 CST-day

编写脚本测试${}

# 使用脚本测试
[root@mico ~]# vim variable.sh
#!/bin/bash time=`date`
echo $timeday
echo ${time}day [root@mico ~]# sh variable.sh 2019年 07月 18日 星期四 17:49:51 CSTday
名称 解释
单引号 所见即所得,即输出时会将单引号内的所有内容都原样输出,或者描述为单引号里面看到的是什么就会输出什么;这称为强引用
双引号(默认) 输出双引号内的所有内容;如果内容中有命令、变量、特殊字符等,会先把变量、命令、转义字符解析出结果,然后再输出最终内容,推荐使用,称为弱引用
无引号 赋值时,如果变量内容中有空格,则会造成赋值不完整。而在输出内容时,会将含有空格的字符串视为一个整体来输出;如果内容中有命令、变量等,则会先把变量、命令解析出结果,然后输出最终内容;如果字符串中带有空格等特殊字符,则有可能无法完整的输出,因此需要改加双引号
反引号 一般用于引用命令,执行的时候命令会被执行,相当于`$()`,赋值和输出都要用命令引起来

定义变量名技巧

  • 变量名只能为字母、数字或下划线,只能以字母或下划线开头。
  • 变量名的定义要有一定的规范,并且要见名知意。

示例:

MicoAge=22       #<==每个单词的首字母大写的写法
mico_age=22 #<==单词之间用"_"的写法
micoAgeSex=man #<==驼峰语法:首个单词的首字母小写,其余单词首字母大写
MICOAGE=22 #<==单词全大写的写法
  • 一般的变量定义、赋值常用双引号;简单连续的字符串可以不加引号;希望原样输出时使用单引号。
  • 希望变量的内容是命令的解析结果时,要用反引号'',或者用$()把命令括起来再赋值。

特殊变量

位置变量

常用的特殊位置参数说明

位置变量 作用说明
$0 获取当前执行的shell脚本的文件名,如果执行脚本带路径那么就包括脚本路径。
$n 获取当前执行的shell脚本的第n个参数值,n=1..9,当n为0时表示脚本的文件名,如果n大于9用大括号括起来{10},参数以空格隔开。
$# 获取当前执行的shell脚本后面接的参数的总个数
$* 获取当前shell的所有传参的参数,不加引号同$@;如果给$加上双引号,例如: “$”,则表示将所有的参数视为单个字符串,相当于“112$3”。
$@ 获取当前shell的所有传参的参数,不加引号同$*;如果给$@加上双引号,例如: “$@”,则表示将所有参数视为不同的独立字符串,相当于“$1” “$2” “$3” “……”,这是将参数传递给其他程序的最佳方式,因为他会保留所有内嵌在每个参数里的任何空白。

$*$@ 都加双引号时,两者有区别,都不加双引号时,两者无区别。

举例说:

脚本名称叫 test.sh 入参三个: 1 2 3

运行 test.sh 1 2 3后

$* 为 "1 2 3"(一起被引号包住)

$@ 为 "1" "2" "3"(分别被包住)

参数实践
[root@mico ~]# vim parameter.sh
#!/bin/bash echo $0
echo "第一个参数:" $1
echo "第二个参数:" $2
echo "第11个参数:" ${11} [root@mico ~]# sh parameter.sh
parameter.sh
第一个参数:
第二个参数:
第11个参数:
[root@mico ~]#
[root@mico ~]# sh parameter.sh 1 2 3 4 5 6 7 8 9 10 11
parameter.sh
第一个参数: 1
第二个参数: 2
第11个参数: 11
[root@mico ~]#
$# 参数实践
[root@mico ~]# cat parameter.sh
#!/bin/bash
##############################################################
# File Name: parameter.sh
# Version: V1.0
# Author: mico
# Created Time : 2019-07-18 17:50:45
# Description:
##############################################################
echo $0
echo "第一个参数:" $1
echo "第二个参数:" $2
echo "第10个参数:" ${10}
echo "第11个参数:" ${11}
echo "参数个数:" $# [root@mico ~]# sh parameter.sh 1 2 3 4 5 6 7 8 9 10 11
parameter.sh
第一个参数: 1
第二个参数: 2
第10个参数: 10
第11个参数: 11
参数个数: 11
$* 参数实践
[root@mico ~]# vim parameter.sh

#!/bin/bash
##############################################################
# File Name: parameter.sh
# Version: V1.0
# Author: mico
# Created Time : 2019-07-18 17:50:45
# Description:
##############################################################
echo $0
echo "第一个参数:" $1
echo "第二个参数:" $2
echo "第10个参数:" ${10}
echo "第11个参数:" ${11}
echo "参数个数:" $#
echo "参数:"$*
[root@mico ~]# sh parameter.sh 1 2 3 4 5 6 7 8 9 10 11
parameter.sh
第一个参数: 1
第二个参数: 2
第10个参数: 10
第11个参数: 11
参数个数: 11
参数:1 2 3 4 5 6 7 8 9 10 11
$*$@ 对比实践
[root@mico scripts]# set -- "I am" handsome boy..
[root@mico scripts]# echo $1
I am
[root@mico scripts]# echo $2
handsome
[root@mico scripts]# echo $3
boy..
[root@mico scripts]# echo $*
I am handsome boy..
[root@mico scripts]# echo $@
I am handsome boy.. [root@mico scripts]# for i in $*;do echo $i ;done
I
am
handsome
boy..
[root@mico scripts]# for i in $@;do echo $i ;done
I
am
handsome
boy..
[root@mico scripts]# for i in "$@";do echo $i ;done
I am
handsome
boy..
[root@mico scripts]# for i in "$*";do echo $i ;done
I am handsome boy..

进程状态变量

Shell进程的特殊状态变量说明

位置变量 作用说明
$? 获取执行上一个指令的执行状态返回值(0为成功,非零为失败),这个变量最常用
$$ 获取当前执行的Shell脚本的进程号(PID),这个变量不常用,了解即可
$! 获取上一个在后台工作的进程的进程号(PID),这个变量不常用,了解即可
$_ 获取在此之前执行的命令或脚本的最后一个参数,这个变量不常用,了解即可

进程参数实践

[root@mico scripts]# echo $?
0
[root@mico scripts]# echo $$
1368
[root@mico scripts]# echo $! [root@mico scripts]# echo $_
Echo

Bash Shell常见的内部命令:echoevalexecexportreadshift

echo参数说明

参数 参数说明
-n 不要追加换行
-e 启用下列反斜杠转义的解释
-E 显式地抑制对于反斜杠转义的解释

`echo' 对下列反斜杠字符进行转义:

  • \n:换行
  • \r:回车
  • \t:横向制表符
  • \b:退格
  • \v:纵向制表符
  • \c:抑制更多的输出

定义变量的方式

三种定义变量的方式

  • 直接赋值
  • 传参 (传递参数)
  • 交互式设置变量,使用read命

read命令说明

在命令行中使用

[root@mico scripts]# read
132
[root@mico scripts]# echo $REPLY
132
[root@mico scripts]# read mico
456
[root@mico scripts]# echo $mico
456
[root@mico scripts]# echo $REPLY
132

在脚本中使用

[root@mico scripts]# vim mico_test.sh
#!/bin/bash
read -p '请输入:' mico echo $mico
执行结果
[root@mico scripts]# sh mico_test.sh
请输入:mico_znix
mico_znix

read 命令的帮助说明

[root@mico scripts]# read --help
-bash: read: --: 无效选项
read: 用法:read [-ers] [-a 数组] [-d 分隔符] [-i 缓冲区文字] [-n 读取字符数] [-N 读取字符数] [-p 提示符] [-t 超时] [-u 文件描述符] [-s不显示终端的任何输入] [名称 ...]

定义方法实践

直接赋值方法

[root@mico ~]# cat parameter.sh
#!/bin/bash
##############################################################
# File Name: parameter.sh
# Version: V1.0
# Author: mico
# Created Time : 2019-07-18 17:50:45
# Description:
##############################################################
name=MICO
age=18
sex=girl
hobby=`ls`
ethFile=/etc/sysconfig/network-scripts/ifcfg-eth0 echo $hobby
ls $ethFile
[root@mico ~]# sh parameter.sh
default.pass Django-Blog Django-Blog.tar parameter.sh practices scripts server test.sh variable.sh
/etc/sysconfig/network-scripts/ifcfg-eth0

交互式设置变量 read

[root@mico ~]# vim keyword.sh
#!/bin/bash
##############################################################
# File Name: keyword.sh
# Version: V1.0
# Author: mico
# Created Time : 2019-07-18 18:42:31
# Description:
##############################################################
read -p "请输入您的学号:" number
read -s -p "请输入姓名:" name
echo
echo "您的学号:"$number
echo "您的姓名:"$name
[root@mico ~]# sh keyword.sh
请输入您的学号:20190729
请输入姓名:
您的学号:20190729
您的姓名:youka

写一个交互脚本,实现能够定义主机名及IP地址

脚本内容↓

[root@mico ~]# cat keyword.sh
#!/bin/bash
##############################################################
# File Name: keyword.sh
# Version: V1.0
# Author: mico
# Created Time : 2019-07-18 18:42:31
# Description:
##############################################################
read -p "请输入您的学号:" number
read -s -p "请输入姓名:" name
echo
echo "您的学号:"$number
echo "您的姓名:"$name [root@mico ~]# vim host_start.sh
[root@mico ~]# sh host_start.sh
请输入主机名:mico
请输入IP地址的主机位:180
是否重启服务器:{yes/no}no
请稍后手动重启系统!

变量的子串

变量子串说明

表达式 说明
${parameter} 返回变量$parameter的内容
${#parameter} 返回变内容的长度(按字符),也适用于特殊变量
${parameter:offset} 在变量${parameter}中,从位置offset之后开始提取子串到结尾
${parameter:offset:length} 在变量${parameter}中,从位置offset之后开始提取长度为length的子串
${parameter#word} 从变量${parameter}开头开始删除最短匹配的word子串
${parameter##word} 从变量${parameter}开头开始删除最长匹配的word子串
${parameter%word} 从变量${parameter}结尾开始删除最短匹配的word子串
${parameter%%word} 从变量${parameter}结尾开始删除最长匹配的word子串
${parameter/pattem/string} 使用string代替第一个匹配的pattern
${parameter//pattem/string} 使用string代替所有匹配的pattern

计算变赋值的长度

  • echo $(#Mico) :这种方式最快
  • echo $Mico|wc -L
  • expr length "$Mico"
  • echo "$Mico"|awk '{print length($0)}'
[root@mico ~]# export TEMP="Here is Linux"
[root@mico ~]# echo ${#TEMP}
13
[root@mico ~]# echo $TEMP|wc -L
13
[root@mico ~]# expr length "$TEMP"
13
[root@mico ~]# echo "$TEMP"
Here is Linux
[root@mico ~]# echo "$TEMP"|awk '{print length($0)}'
13
[root@mico ~]# time echo ${#TEMP}
13 real 0m0.000s
user 0m0.000s
sys 0m0.000s
[root@mico ~]# time echo $TEMP|wc -L
13 real 0m0.002s
user 0m0.000s
sys 0m0.002s
[root@mico ~]#

截取变量中的字符

[root@mico scripts]# mico=abcABC123ABCabc
[root@mico scripts]# echo ${mico#abc}
ABC123ABCabc
[root@mico scripts]# echo ${mico##abc}
ABC123ABCabc
[root@mico scripts]# echo ${mico%abc}
abcABC123ABC
[root@mico scripts]# echo ${mico%%abc}
abcABC123ABC
[root@mico scripts]# echo ${mico#a*c}
ABC123ABCabc
[root@mico scripts]# echo ${mico##a*c} [root@mico scripts]# echo ${mico%a*c}
abcABC123ABC
[root@mico scripts]# echo ${mico%%a*c} [root@mico scripts]# echo ${mico#a*C}
123ABCabc
[root@mico scripts]# echo ${mico#a*C}
123ABCabc
[root@mico scripts]# echo ${mico##a*C}
abc
[root@mico scripts]# echo ${mico%a*c}
abcABC123ABC
[root@mico scripts]# echo ${mico%A*c}
abcABC123
[root@mico scripts]# echo ${mico%%A*c}
abc

替换变量内容

[root@mico scripts]# echo $mico
abcABC123ABCabc
[root@mico scripts]# echo ${mico/abc/mico}
micoABC123ABCabc
[root@mico scripts]# echo ${mico//abc/mico}
micoABC123ABCmico

echo $(#parameter) 处理最快的原因是一般情况下调用外部命令来处理的方式与使用内置操作的速度相差较大。在Shell编程中应该尽量使用内置命令。

有关获取字符串长度的几种统计方法的性能比较

  • 变量自带的计算长度的方法效率最高,在要求效率的场景中尽量多用
  • 使用管道统计的方法的效率都比较差,在要求效率的场景中尽量不用
  • 对于日常简单的脚本计算,可以根据自己所擅长的或易用的程度去选择

有关上述匹配删除的小结

+#表示从幵头删除匹配最短。

  • ##表示从开头删除匹配最长。
  • %表示从结尾删除匹配最短。
  • %%表示从结尾删除匹配最长。
  • a*c表示匹配的突符串,*表示匹配所有,a*c 匹配开头为 a、中间为任意多个字符、结尾为c的字符串。
  • a*C表示匹配的字符串,*表示匹配所有,a*C 匹配开头为 a、中间为任意多个字符、结尾为C的字符串。

有关替换的小结

  • 一个“/”表示替换匹配的第-个字符串。
  • 两个“/”表示替换匹配的所有字符串。

Shell的特殊扩展变量说明

表达式 说明
${parameter:-word} 如果parameter的变量值为空或未赋值,则会返回word字符串并替代变量的值用途.如果变量未定义,则返回备用的值,防止变量为空值或因未定义而导致异常
${parameter:=word} 如果parameter的变量值为空或未赋值,则设置这个变量值为word,并返回其值。位置变量和特殊变量不适用用途:基本同上一个${parameter>word},但该变量又额外给parameter变量赋值了
${parameter:?word} 如果parameter变量值为空或未赋值,那么word字符串将被作为标准错误输出,否则输出变量的值。用途:用于捕捉由于变量未定义而导致的错误,并退出程序
${parameter:+word} 如果parameter变量值为空或未赋值,则什么都不做,否则word字符串将替代变量的值

变量的数值计算

Shell中常见的算术运算符号

运算符 意义 顺序
++ - - 增加及减少,可前置/可结尾
+ 一元正号
- 一元负号
逻辑与
~ 位的取反
* / % 乘法、除法、取余数
+ 加法
- 减法
<< >> 向左位移、向右位移
< <= > >= 比较运算符
== != 相等 不相等
& 位的AND
^ 位的Exclusive OR
| 位的OR
&& 逻辑AND
|| 逻辑OR
?: 条件表达式
= += -= *= /= %= &= ^= 赋值运算符

Shell中常见的算术运算命令

运算操作符与运算命令 意义
(()) 用于整数运算的常用运算符,效率高
let 用于整数运算,类似于"(())"
expr 可用于整数运算,但还有很多其他的额外功能
bc Linux下的一个计算器程序(适合整数以及小数运算)
$[] 用于整数的运算
awk awk既可以用于整数运算,也可以用于小数运算
declare 定义变量值和属性,-i参数可以用于定义整形变量,做运算

双小括号(())的操作方法

运算操作符与运算命令 意义
((i=i+1)) 此种书写方法为运算后赋值法,即将i+1的运算结构赋值给变量i。注意,不能用"echo((i=i+1))"的形式输出表达式的值,但可以用echo$((i=i+1))输出其值
i=$((i+1)) 可以在"(())"前加$符,表示将表达式运算后复制给i
((8>7&&5==5)) 可以进行比较操作,还可以加入逻辑与和逻辑或,用于条件判断
echo $((2+1)) 需要直接输出运算表达式的运算结果时,可以在"(())"前加$符

【提示】上面涉及的数字及变量必须为整数(整型),不能是小数(浮点数)或字符串。后面的bcawk命令可以用于进行小数(浮点数)运算,但一般用到的较少。

仅支持整数的运算

echo $((数学运算表达式))
# 形式一
[root@mico scripts]# echo $((1 + 1))
2
[root@mico scripts]# echo $((2*7-3/6+5))
19
# 形式二
[root@mico scripts]# ((mico=2*8))
[root@mico scripts]# echo $mico
16
# 形式三
[root@mico scripts]# znix=$((2*7-3/6+5))
[root@mico scripts]# echo $znix
19
延伸产物(重要)
  • i++ 自增1
  • i-- 自减1
  • ++i
  • --i

示例:

[root@mico scripts]# i=1
[root@mico scripts]# echo $((i++))
1
[root@mico scripts]# echo $((i++))
2
[root@mico scripts]# echo $((i--))
3
[root@mico scripts]# echo $((i--))
2
[root@mico scripts]# echo $((i--))
1
[root@mico scripts]# echo $((++i))
1
[root@mico scripts]# echo $((++i))
2
[root@mico scripts]# echo $((++i))
3
[root@mico scripts]# echo $((--i))
2
[root@mico scripts]# echo $((--i))
1
[root@mico scripts]# echo $((--i))
0

记忆方法:++,--

变量a在前,表达式的值为a,然后a自增或自减,变量a在符号后,表达式值自增或自减,然后a值自增或自减。

let命令

[root@mico scripts]# i=1
[root@mico scripts]# i=i+1
[root@mico scripts]# echo $i
i+1 [root@mico scripts]# i=1
[root@mico scripts]# let i=i+1
[root@mico scripts]# echo $i
2

expr 命令

  • 整数计算
  • 判断扩展名
  • 判断输入是否为整数,非整数返回值为2
  • 计算变量的长度
示例
[root@mico scripts]# expr 1+1
1+1
[root@mico scripts]# expr 1 + 1
2
[root@mico scripts]# expr 1 * 1
expr: 语法错误
[root@mico scripts]# expr 1 \* 1
1
非整数返回值为2 示例:
[root@mico scripts]# expr 1 + 1
2
[root@mico scripts]# echo $?
0
[root@mico scripts]# expr -1 + 1
0
[root@mico scripts]# echo $?
1
[root@mico scripts]# expr a + 1
expr: 非整数参数
[root@mico scripts]# echo $?
2
$[]运算符
[root@mico scripts]# echo $[1+2]
3
[root@mico scripts]# echo $[1-2]
-1
[root@mico scripts]# echo $[1*2]
2
[root@mico scripts]# echo $[1/2]
0

typeset 命令进行运算

[root@mico scripts]# typeset -i A=2017 B=2018
[root@mico scripts]# A=A+B
[root@mico scripts]# echo $A
4035

可以进行小数运算的命令

bc 命令

# 安装 bc  依赖于base源
[root@mico scripts]# yum -y install bc
交互模式测试bc命令
[root@mico scripts]# bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
1+1
2
[root@mico scripts]# echo 1+1.1|bc
2.1

免交互模式测试bc命令

[root@mico scripts]# echo 'scale=6;1/3'|bc
.333333
python 命令
[root@mico scripts]# file `which yum `
/usr/bin/yum: Python script, ASCII text executable
[root@mico scripts]# python
>>> import os
>>> os.system('df -h')
>>> 1+1.1
2.1
>>>exit()

awk 命令

[root@mico ~]# echo "7.7 3.8"|awk '{print ($1-$2)}'
3.9
[root@mico ~]# echo "358 113"|awk '{print ($1-3)/$2}'
3.14159
[root@mico ~]# echo "3 9"|awk '{print ($1+3)*$2}'
54
[root@backup scripts]# awk BEGIN'{print 1.2+3.3}'
4.5

运算相关练习题

【练习题】实现一个加减乘除等功能的计算器

实现脚本:

[root@mico scripts]# cat calculator.sh
#!/bin/bash read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b echo $a + $b =$(($a+$b))
echo $a - $b =$(($a-$b))
echo $a \* $b =$(($a*$b))
echo $a / $b =$(($a/$b))
脚本执行过程:
[root@mico scripts]# sh calculator.sh
请输入第一个整数:12
请输入第二个整数:12
12 + 12 =24
12 - 12 =0
12 * 12 =144
12 / 12 =1
精简方法
[root@mico scripts]# vim calculator2.sh
#!/bin/bash echo $(($1))
脚本执行过程:
[root@mico scripts]# sh calculator2.sh 1+1
2
[root@mico scripts]# sh calculator.sh 1*9
9

【练习题】打印结果1+2+3+4+5+6+7+8+9+10=55

脚本内容

[root@mico scripts]# vim count.sh
#!/bin/bash Num=`seq -s + 1 10`
echo $Num=$(($Num))

脚本执行结果

[root@mico scripts]# sh  count.sh
1+2+3+4+5+6+7+8+9+10=55

补充说明

shell脚本中批量注释的方法

<<'EOF'

文件内容

EOF 或使用 exit 可以注释其之后的所有内容(类似注释,实质为不执行后面的内容)

上一篇:java面向对象---对象容器


下一篇:重磅!Google推出了Python最牛X的编辑器......