在Linux文本编辑器的江湖中一直有一个神器Vim,这个神奇可以满足日常编辑的所有需求。通过Vim可以进一步的学习关于Shell的脚本,提升操作Linux效率。

1文本编辑器

1.1Nano

一个自带的文本编辑器(大多数情况下不使用)

快捷键在编辑器的底部

Nano界面图 Nano界面图

1.2配置文件/etc/nanorc

Linux 或 Unix 的许多程序在启动时,都需要 rc 后缀的初始文件或配置文件。rc,它是runcomm的缩写,即 run command(运行命令)的简写。熟悉Android架构的小伙伴会比较熟悉,系统各种服务都是靠着rc文件启动的。

1#/etc/nanorc
2set historylog#激活历史日志
3set locking#激活锁定
4set nowrap#激活不隐藏输入
5set suspend#激活拼接模式
6include "/usr/share/nano/*.nanorc"

一共五条语句,每一行一句配置语句,配置语句是以 set(用于激活。set 是英语“放置,设置”的意思)或 unset(用于关闭)开头,后接你要配置的项目。

1#激活鼠标
2set mouse
3#激活首行缩进
4set autoindent

对于每个用户来说,家目录下的 .bashrc 文件的优先级比系统的 /etc/bash.bashrc 文件高。例如同样的配置选项,如果 .bashrc 和 /etc/bash.bashrc 不同,那么以 .bashrc 的为准。同样的原则也适用于其它配置文件,例如 .nanorc 和 /etc/nanorc。

如果我们修改了 .bashrc 和 profile 文件后,默认是在用户下次登录系统时才能生效。但是我们可以用 source 命令来使改动立即生效

1source .bashrc
2source .profile

1.2Vim

文本编辑器的进阶版,比前者更加简单实用。(实际用得比较多)

1.2.1Vim的界面

打开vim之后的界面如下图所示,可以看到有光标,界面,还有尾行说明。

光标指向的地方,会在尾行说明中体现出来,前面1,1代表第一行第一列,后面会显示百分比或者是Top(文本的头部)或者是Bot(文本的尾部)。

Vim界面图 Vim界面图

1.2.2Vim的相关配置

 1#定义自己的vim
 2cp /etc/vim/vimrc ~/.vimrc
 3#./vimrc
 4#配置语法高亮
 5syntax on
 6#背景着色为黑色
 7set background=dark
 8#显示行号
 9set number
10#显示当前命令
11set showcmd
12#忽略大小写搜索
13set ignorecase
14#设置鼠标使用
15set mouse=a

以下对Vim的特性和shell脚本展开说明。

2Vim

2.1Vim的三种工作模式

  1. 交互模式:Interactive Mode。这是 Vim 的默认模式,每次我们运行 Vim 程序的时候,就会进入这个模式。复制粘贴文本,跳转到指定行,撤销操作等等

  2. 插入模式:Insert Mode。这就是我们熟悉的文本编辑器的“一贯作风”。我们输入文本,文本就被插入到光标所在之处。为了进入这个模式,有几种方法,最常用的方法是按字母键 i(i 是 insert 的首字母,是英语“插入”的意思,除了i之外,还有五种a,A,s,S,I)。为了退出这种模式,只需要按下 Esc 键(一般在键盘左上角)。Esc 是 escape 的缩写,是英语“脱离,逃脱”的意思。左下角有一个 -- INSERT --,说明我们就处在 Insert Mode(插入模式)中。

    关于插入的i,I,a,A,s和S的区别

    1.i是当前插入

    2.I是当前行首插入

    3.a会往后移动一个字符插入

    4.A是当前行尾插入

    5.s是删除当前字符插入

    6.S是删除当前行插入

  3. 命令模式:Command Mode。也有称之为底线命令模式(Last line mode)的。这个模式下,我们可以运行一些命令,例如“退出”、“保存”等等。也可以用这个模式来激活一些 Vim 的配置(例如语法高亮、显示行号等等)。

2.1.1命令模式

表2.1命令模式输入

命令模式输入 含义
:0 移动到行首
:$ 移动到行末
:w 保存文件
:q 直接退出
:q! 强制退出,不保存修改
:wq/:x 保存然后退出
:行号 跳转到指定行
/ 查找。n 键,查找下一个匹配项Shift + n,查找上一个匹配
/\<string_to_exact_match\> 查找全匹配字符串
:s 查找并替换
:s/旧字符串/新字符串 替换光标所在行的第一个匹配的字符串
:s/旧字符串/新字符串/g 替换光标所在行的所有匹配的旧字符串为新字符串
:%s/旧字符串/新字符串/g 替换文件中所有匹配的字符串
:sp split,横向分屏
:vsp 垂直分屏
:!命令 运行外部命令

除了上面常用之外,还有以命令模式激活选项参数,可以参考1.2.2的Vim的相关配置。

 1# “短暂性”配置选项参数
 2:set 选项名
 3# 而不激活(取消)一个选项参数
 4:set no 选项名
 5# 查询选项的参数
 6:set 选项名?
 7# 输出结果为background=light
 8:set background?
 9
10# 几个常见的设置命令
11# 显示当前命令
12:set showcmd
13# 显示行号
14:set number
15# 在查找时忽略大小写
16:set ignorecase
17# 鼠标支持
18:set mouse=a

2.1.2交互模式

表2.2交互模式控制方向的按键

按键 作用
h/左键 向左移动一个字符
j/下键 向下移动一个字符
k/上键 向上移动一个字符
l/右键 向右移动一个字符

表2.3交互模式输入

交互模式命令 含义
w 一个单词一个单词地移动
x 删除字符
dd 删除行,可以结合p粘贴一起使用
dw 删除一个单词
d0 删除从光标处到行首的所有字符
d$ 删除从光标处到行末的所有字符
yy yank,复制行到内存中。和 dd 类似,dd 用于“剪切”光标所在行到内存中,而 yy 是“复制”。可以结合p粘贴一起使用
yw 复制一个单词
y$ 复制从光标所在处到行末的所有字符
y0 复制从光标所在处到行首的所有字符
p 粘贴,可以配合dd或者yy
r 替换一个字符
u 撤销操作
Ctrl + r 取消撤销,也就是重做之前的修改,redo
Shift + g 要跳转到最后一行
gg 要跳转到第一行
行号 + Shift + g 跳转到指定行(间隔时间短,不好控制,可以直接进入命令模式:行号跳转)
行号 +gg 跳转到指定行(间隔时间短,不好控制,可以直接进入命令模式:行号跳转)

2.1.3交互模式

考虑到插入模式里面可以多用来输入,所以本文不具体展开。

2.1.4 其他

除了上述模式用到的常用命令之外,还可以通过按F1,获取更多的信息。

例如,跳转到指定行除了上述两种方式之外,还可以在vim的时候添加行号vim file +行号

3shell

shell 是英语“壳,外壳”的意思。你可以把它想象成嵌入在 Linux 这样的操作系统中的一个“微型编程语言”。

Shell 不像 C 语言,C++,Java 等编程语言那么完整,不需要安装,不需要编译,但是 Shell 这门语言可以帮我们完成很多自动化任务,例如:保存数据,监测系统的负载等等。

3.1主流的shell

几种主流的 Shell:

  • Sh : Bourne Shell 的缩写。可以说是目前所有 Shell 的祖先。
  • Bash : Bourne Again Shell 的缩写, 可以看到比 Bourne Shell 多了一个 again。again 在英语中是“又,再,此外”的意思,说明 Bash 是 Sh 的一个进阶版本,比 Sh 更优秀。Bash 是目前大多数 Linux 发行版和苹果的 macOS 操作系统的默认 Shell。
  • Ksh : Korn Shell 的缩写。一般在收费的 Unix 版本上比较多见,但也有免费版本的。
  • Csh : C Shell 的缩写。 此 Shell 的语法有点类似 C 语言。
  • Tcsh : Tenex C Shell 的缩写。Csh 的优化版本。
  • Zsh : Z Shell 的缩写。比较新近的一个 Shell,集 Bash,Ksh 和 Tcsh 各家之大成。

可以看到,箭头的源头就是来自Sh,所有的shell都是源于Sh,目前主流用的最多的就是Bash。

3.2shell的用途

事实上,Shell 提供了所有可以让你运行命令的基础功能。通过Shell 这个桥梁,去操控作用系统内核(内核的英语是 kernel)。

为了切换Shell,需要用到以下命令:

1chsh

4shell脚本

我们之前用得到所有命令,都是依赖于shell的。有时候我们需要去批量,自动化的处理一些交互事务,每次手动去敲命令显得有点突兀,这个时候需要用到的是shell脚本。

4.1创建脚本文件

我们用 Vim 这个文本编辑器来创建一个 Shell 脚本文件,这里可以命名sh的后缀,强调这是一个 Shell 脚本文件。

1vim test.sh

脚本有特定的语法

1# 指定运行脚本用得shell
2#!/bin/bash
3#输入最简单的指令
4ls

4.2运行脚本

1# 添加脚本可执行权限
2chmod +x test.sh
3#运行脚本
4./test.sh

结果

4.3调试模式运行

会对shell脚本里的每条指令单独分析。其中每一条命令前面都会有一个标识符+,然后对应会有命令的结果。

1bash -x test.sh

4.4基本运算

4.4.1定义变量

1#特别注意,在等号两边不要加空格。
2#错误 a = "Hello world"
3a="Hello world"

4.4.2显示内容

1echo "Hello World"
2#-e 参数,为了使“转义字符”发生作用
3echo -e "First line\nSecond line"

4.4.3显示变量

1echo $a
2#下面显示变量的方式有点类似kotlin的用法
3echo "The message is $a"

4.4.4引号

分成三种,单引号,双引号和反引号。

  1. 单引号:忽略被它括起来的所有特殊字符

  2. 双引号:忽略大多数特殊字符,除了$\ ,`(美元符号,反斜杠,反引号)

  3. 反引号:要求 Shell 执行被它括起来的内容

1# 单引号
2a='Hello World'
3echo 'The message is $a'
4echo "The message is $a"
5dir=`pwd`
6echo "The dir is $dir"

4.4.5请求输入

类似c++中的cin

 1read msg
 2echo "Hello, $msg"
 3read Date Time
 4echo "today is $a, $b"
 5# 显示输入提示
 6read -p 'This is a shell learn' action
 7echo "my shell echo :$action"
 8# 限制字符数
 9read -p 'name limit is 8' -n 8 limit_length
10echo "\nmy shell echo :$limit_length"
11# 限制输入事件
12read -p 'time is limit 10 second, you must hurry up' -t 10 limit_time
13echo -e "\nBoom !"
14echo "\nmy shell echo :$limit_time"
15# 隐藏输入内容
16read -p 'you input secret :' -s password
17echo "\nOk, passwd is $password"

4.4.6数学运算

在 Bash 中,所有的变量都是字符串,需要特定字符让其运算。

1# let 命令可以用于赋值
2let "num1 = 1"
3let "num2 = 1"
4let "num3 = num1 + num2"
5echo "num3 = $num3"

具体数学运算如下表所示

运算 符号
+
-
*
/
**
%

这些跟基本的编程符号相似,所以不展开描述。

1# 下面两种方式是一样的
2a=1
3let "a = a * 3"
4let "a *= 3"

4.4.7任意精度计算

bc 命令是任意精度计算器语言,通常在linux下当计算器用

 1# 方式1,直接输入bc,在bc模式下运算,一般根据你的输入精度来对应输出精度,通过输入quit回车后退出
 2bc
 3sqrt(3.0000000000*10^0)
 41.7320508075
 5# 方式2,通过管道
 6# scale=2,代表保留2位
 7echo 'scale=2; (2.777 - 1.4744)/1' | bc
 8# 通过ibase和Obase来进制转化,原始进制位ibase,目标进制位Obase
 9echo "ibase=2;111" |bc
10abc=192 
11echo "obase=2;$abc" | bc
12abc=11000000 
13echo "obase=10;ibase=2;$abc" | bc

4.4.8环境变量

几个常见的环境变量,默认环境变量都是大写的

SHELL:指明目前你使用的是哪种 Shell。我目前用的是 Bash(因为 SHELL=/bin/bash)。

PATH:是一系列路径的集合。只要有可执行程序位于任意一个存在于 PATH 中的路径,那我们就可以直接输入可执行程序的名字来执行。

1echo $PATH

HOME:你的家目录所在的路径。

PWD:目前所在的目录。

4.4.8.1环境变量的分类

环境变量可以简单的分成用户自定义的环境变量以及系统级别的环境变量。

  • 用户级别环境变量定义文件:~/.bashrc~/.profile(部分系统为:~/.bash_profile
  • 系统级别环境变量定义文件:/etc/bashrc/etc/profile(部分系统为:/etc/bash_profile)、/etc/environment

另外在用户环境变量中,系统会首先读取~/.bash_profile(或者~/.profile)文件,如果没有该文件则读取~/.bash_login,根据这些文件中内容再去读取~/.bashrc

4.4.8.2环境变量加载顺序

具体顺序如下图所示,其中红色代表系统环境变量文件,绿色是当前用户文件

目前开始的时候或从/etc/envirnment开始加载,然后加载/etc/profile和~/.profile。

其中/etc/profile获取加载/etc/bash.bashrc和/etc/profile.d/*.sh

 1# /etc/profile,系统的环境变量
 2# 这里先后加载了/etc/bash.bashrc和/etc/profile.d/*.sh
 3if [ "$PS1" ]; then
 4  if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
 5    # The file bash.bashrc already sets the default PS1.
 6    # PS1='\h:\w\$ '
 7    if [ -f /etc/bash.bashrc ]; then
 8      . /etc/bash.bashrc
 9    fi
10    ...
11fi
12
13if [ -d /etc/profile.d ]; then
14  for i in /etc/profile.d/*.sh; do
15    if [ -r $i ]; then
16      . $i
17    fi
18  done
19  unset i
20fi

然后~/.profile会加载~/.bashrc,可以知道每次运行shell的时候都会去加载一次~/.bashrc。从~/.profile文件中代码不难发现,~/.profile文件只在用户登录的时候读取一次,而~/.bashrc会在每次运行Shell脚本的时候读取一次

1# if running bash
2if [ -n "$BASH_VERSION" ]; then
3    # include .bashrc if it exists
4    if [ -f "$HOME/.bashrc" ]; then
5        . "$HOME/.bashrc"
6    fi
7fi
8
9PATH="$HOME/bin:$HOME/.local/bin:$PATH"

4.4.8.3环境变量配置

在配置之前,需要确定原先有哪些环境配置

1export

这里罗列6种方式,有命令方式和修改配置文件方式,通常设置配置文件为永久性的修改

  1. export PATH

    使用export命令直接修改PATH的值,例如配置jdk的路径。

    1export PATH=/home/yangyang/jdk1.8.0_311/bin:$PATH
    2# 或者另外一种写法
    3export PATH=$PATH:/home/yangyang/jdk1.8.0_311/bin
    
    • 生效时间:立即生效
    • 生效期限:当前终端有效,窗口关闭后无效
    • 生效范围:仅对当前用户有效
    • 配置的环境变量中不要忘了加上原来的配置,即$PATH部分,避免覆盖原来配置
  2. vim ~/.bashrc

    通过修改用户目录下的~/.bashrc文件进行配置

    1# 在最后一行加上
    2export PATH=$PATH:/home/yangyang/jdk1.8.0_311/bin
    
    • 生效时间:使用相同的用户打开新的终端时生效,或者手动source ~/.bashrc生效
    • 生效期限:永久有效
    • 生效范围:仅对当前用户有效
    • 如果有后续的环境变量加载文件覆盖了PATH定义,则可能不生效
  3. vim ~/.profile

    和修改~/.bashrc文件类似,也是要在文件最后加上新的路径

    1# 在最后一行加上
    2export PATH=$PATH:/home/yangyang/jdk1.8.0_311/bin
    
    • 生效时间:使用相同的用户打开新的终端时生效,或者手动source ~/.profile生效
    • 生效期限:永久有效
    • 生效范围:仅对当前用户有效
    • 如果没有~/.profile文件,则可以编辑~/.profile文件或者新建一个
  4. vim /etc/bashrc

    该方法是修改系统配置,需要管理员权限(如root)或者对该文件的写入权限,笔者没有这个文件,所以这一步修改不了

    1# 在最后一行加上
    2export PATH=$PATH:/home/yangyang/jdk1.8.0_311/bin
    
    • 生效时间:新开终端生效,或者手动source /etc/bashrc生效
    • 生效期限:永久有效
    • 生效范围:对所有用户有效
  5. vim /etc/profile

    该方法修改系统配置,需要管理员权限或者对该文件的写入权限,通常笔者配置环境变量都选择用该方式

    1# 在最后一行加上
    2export PATH=$PATH:/home/yangyang/jdk1.8.0_311/bin
    
    • 生效时间:新开终端生效,或者手动source /etc/profile生效
    • 生效期限:永久有效
    • 生效范围:对所有用户有效
  6. vim /etc/environment

    该方法是修改系统环境配置文件,需要管理员权限或者对该文件的写入权限

    1# 在最后一行加上
    2export PATH=$PATH:/home/yangyang/jdk1.8.0_311/bin
    
    • 生效时间:新开终端生效,或者手动source /etc/environment生效
    • 生效期限:永久有效
    • 生效范围:对所有用户有效

4.4.9参数变量

1./variable.sh 参数1 参数2 参数3 ...

$# :包含参数的数目。 $0 :包含被运行的脚本的名称 (我们的示例中就是 variable.sh )。 $1:包含第一个参数。 $2:包含第二个参数。 … $8 :包含第八个参数。

shift参数移位

1#!/bin/bash
2
3echo "The first parameter is $1"
4shift
5echo "The first parameter is now $1"

4.4.10数值变量

shell中默认把变量值当作字符串,例如:

1age=22
2age=${age}+1
3echo ${age}

输出结果为22+1,而不是23。

原因是shell将其解释为字符串,而不是数学运算。

有两种方式使其进行数学运算。

1# 用let命令使其进行数学运算
2let age=${age}+1
3# 用declare把变量定义为整型
4declare -i age=22

此后每次运算,都把age的右值识别为算术表达式或数字。

4.4.11数组

定义数组

1array=('value0' 'value1' 'value2')

访问数组元素

1${array[2]}

对数组元素赋值

1array[3]='value3'

Shell 中的数组的下标(index)也基本是从 0 开始的,而不是从 1 开始。因此,第一个元素的编号(下标)就是 0,第二个元素的下标就是 1,以此类推。 不过,也不是所有 Shell 语言的数组下标都是从 0 开始,不少 Shell 语言(例如 Csh,Tcsh,Zsh,等等)的数组下标是从 1 开始的。

可以一次性显示数组中所有的元素值,需要用到通配符 *(星号)

1#!/bin/bash
2
3array=('value0' 'value1' 'value2')
4array[5]='value5'
5echo ${array[*]}
6echo ${array[@]}
7# 获取数组元素个数为: ${#array[@]}或者${#array[*]}
8echo ${#array[@]}
9echo ${#array[*]}

关于shell数组的进阶,包括二维数组,详见这里,本文不展开描述。

特别注意:

${array[@]}和${array[]} 一般情况下都能显示全部的数组元素,但是要实现二维数组的效果,不能使用${array[]}。

4.4.12特殊变量

表4.4.1特殊变量

特殊变量 含义
$0 当前脚本的文件名
$num num为从1开始的数字$1是第一个参数,$2是第二个参数,${10}是第十个参数
$# 传入脚本的参数的个数
$* 所有的位置参数(作为单个字符串)
$@ 所有的位置参数(每个都作为独立的字符串)
$? 当前shell进程中,上一个命令的返回值如果上一个命令成功执行则$?的值为0,否则为其他非零值,常用做if语句条件
$$ 当前shell进程的pid
$! 后台运行的最后一个进程的pid
$- 显示shell使用的当前选项
$_ 之前命令的最后一个参数

4.5条件运算

4.5.1if 条件语句

1if [ condition ]
2then 
3    dosomething
4fi

可以看到condition这个条件为真的时候,就可以dosomething了。

注意:方括号 [] 中的 条件测试 两边必须要空一格。不能写成 [condition],而要写成 [ condition ]

除了上述的写法之外,还有另外一种常见的写法

1if [ condition ]; then
2    dosomething
3fi

如果存在多个if else判断的话,那就是下面的写法方式

1if [ condition1 ];then
2    dosomething1
3elif [ condition2 ];then
4    dosomething2
5else
6    dosomething3
7fi

表4.5.1数值的判断

数值判断语句 判断说明
NUM1 -eq NUM2 NUM1和NUM2两数相等为真 ,=
NUM1 -ne NUM2 NUM1和NUM2两数不相等为真 ,<>
NUM1 -gt NUM2 NUM1大于NUM1为真 ,>
NUM1 -ge NUM2 NUM1大于等于NUM1为真,>=
NUM1 -lt NUM2 NUM1小于NUM1为真 ,<
NUM1 -le NUM2 NUM1小于等于NUM1为真,<=

表4.5.2逻辑判断

逻辑判断语句 判断说明
-a
-o
!

表4.5.3字符串的判断

字符串判断语句 判断说明
[ -z STRING ] 如果STRING的长度为零则为真,len=0
[ -n STRING ]/[ STRING ] 如果STRING的长度非零则为真,len>0
[ STRING1 = STRING2 ] 如果两个字符串相同则为真
[ STRING1 != STRING2 ] 如果字符串不相同则为真

“等于”是用一个等号( = )来表示的,C 语言中“等于”是用两个等号( == )来表示的。但 Shell 中用两个等号来表示“等于”的判断也是可以的。

表4.5.4文件/文件夹的判断

文件/文件夹判断 判断说明
[ -e FILE ] 如果 FILE 存在则为真
[ -f FILE ] 如果 FILE 存在且是一个普通文件则为真
[ -s FILE ] 如果 FILE 存在且大小不为0则为真
[ -r FILE ] 如果 FILE 存在且是可读的则为真
[ -w FILE ] 如果 FILE存在且是可写的则为真
[ -x FILE ] 如果 FILE 存在且是可执行的则为真
[ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真
[ -c FILE ] 如果 FILE 存在且是一个字特殊文件则为真
[ FILE1 -nt FILE2 ] 如果 FILE1的最近修改时间比FILE新或者如果 FILE1存在,FILE2不存在则为真。
[ FILE1 -ot FILE2 ] 如果 FILE1的最近修改时间比 FILE2 老或者 FILE2 存在且,FILE1 不存在则为真。
[ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则为真
[ -d DIR ] 如果 FILE 存在且是一个目录则为真

上面的表知识列举了比较常见的判断,如果遇到非常见的判断,还是需要手册查询。

  • 一.[] 和 test

    两者是一样的。test expr[ expr ] 的效果相同。

    一般常用于判断文件判断字符串判断整数

    字符串比较还是整数比较都千万不要使用 <, >

 1#!/bin/bash
 2var1="1"
 3var2="2"
 4
 5# 下面是并且的运算符-a,另外注意,用一个test命令就可以了,还有if条件后面的分号
 6if test $var1 = "1" -a $var2 = "2" ; then
 7  echo "equal"
 8fi
 9# 下面是或运算符 -o,有一个为真就可以
10if test $var1 != "1" -o $var2 != "3" ; then
11  echo "not equal"
12fi
13# 下面是非运算符 !,if条件是为真的时候执行,如果使用!运算符,那么原表达式必须为false
14if ! test $var1 != "1"; then
15  echo "not 1"
16fi
  • 二.[ ]

    支持字符串的模式匹配(使用 =~ 操作符时甚至支持shell的正则表达式)。逻辑组合可以不使用test-a , -o 而使用 &&, ||

    字符串比较时可以把右边的作为一个模式字符串,不仅仅是一个字符串,比如 [[ hello == hell? ]],结果为真。如果右边的字符串加了双引号,则认为是一个文本字符串。

  • 三.(()) 和 let

    两者是一样的(双括号let稍弱一些)。都是一种结构拓展,用于计算算术表达式的值。

    1.直接使用熟悉的 < , > 等比较运算符。

    2.可以直接使用变量名如 var 而不需要 $var 这样的形式

    3.支持分号隔开的多个表达式

    4.如果表达式值为0,会返回1;如果是非零值的表达式,返回一个0

  • 四.if中[ ]和 区别

    1.[[]]是关键字,许多 shell (如 ash bsh)并不支持这种方式。

    [] 是一条shell命令, 与 test 等价,大多数 shell 都支持。

    2.[[]] 结构比 Bash 版本的 [] 更通用。&&, || , <> 操作符能在一个 [[]] 测试里通过,但在 [] 结构会发生错误。

    3.[[ ... && ... && ... ]][ ... -a ... -a ...] 不一样,[[ ]] 是逻辑短路操作,而 [ ] 不会进行逻辑短路

    4.一般比较两个字符串相等,推荐方式,避免出现"[: =: unary operator expected",原因是可能出现未定义的字符串,字符串为空。

    1# 不推荐的方式[ $STATUS = "OK" ]
    2# 推荐方式
    3[[ "$STATUS"x == "OK"x ]]
    

举例说明运算

1# example1 a>b且a<c,下面三种方式等价
2if (( a > b )) && (( a < c ))
3if [[ $a > $b ]] && [[ $a < $c ]]
4if [ $a -gt $b -a $a -lt $c ]
5# example2 a>b或a<c,下面三种方式等价
6if (( a > b )) || (( a < c ))
7if [[ $a > $b ]] || [[ $a < $c ]]
8if [ $a -gt $b -o $a -lt $c ]

4.5.2case条件语句

linux编程语言中的 switch 语句

 1#!/bin/bash
 2
 3case $1 in
 4    "Cupcake")
 5        echo "Hello Cupcake !"
 6        ;;
 7    "Donut")
 8        echo "Hello Donut !"
 9        ;;
10    "Éclair")
11        echo "Hello Éclair !"
12        ;;
13    "Froyo")
14        echo "Hello Froyo !"
15        ;;
16    *)
17        echo "Sorry, I do not know which version.Maybe Oreo"
18        ;;
19esac
  • case $1 in :$1 表示我们要测试的变量是输入的第一个参数。in 是英语“在…之中”的意思。

  • “Cupcake”) :测试其中一个 case,也就是 $1 是否等于 “Cupcake”。也可以用星号来做通配符来匹配多个字符,例如 “M*") 可以匹配所有以 M 开头的字符串。

  • ;; :类似于主流编程语言中的 break;,表示结束 case 的读取,程序跳转到 esac 后面执行。

  • *) :相当于 if 条件语句的 else,表示“否则”,就是“假如不等于上面任何一种情况”。

  • esac :是 case 的反写,表示 case 语句的结束。

 1#!/bin/bash
 2case $1 in
 3    "dog" | "cat" | "pig")
 4        echo "It is a mammal"
 5        ;;
 6    "pigeon" | "swallow")
 7        echo "It is a bird"
 8        ;;
 9    *)
10        echo "I do not know what it is"
11        ;;
12esac

上面判断变量的分类是dog,cat,pig是一类,pigeon和swallow是另外一类,只要输入的参数是其中之一即可满足这一类别的判断。

出上面之外,还支持正则表达式,判断一个字符

 1#!/bin/bash
 2# 输入一个字符,用于后续判断是字母,数字还是其他类型
 3read -p "press some key ,then press return :" KEY
 4case $KEY in
 5    [a-z]|[A-Z])
 6        echo "It's a letter."
 7        ;;
 8    [0-9]) 
 9        echo "It's a digit."
10        ;;
11    *)
12        echo "It's function keys、Spacebar or other ksys."
13        ;;
14esac

4.5.3条件变量替换

表4.5.5条件变量替换

引用格式 返回值及用法
$var 返回变量值,例如var=“Barry”,则$var即为Barry,这里的"“是界定符号,不是字符串中的字符
${var} 返回变量值,推荐写法。
${#var} 返回该变量字符串的长度,例如var=“Barry”,则${#var}返回5
${var:start_index} 默认返回从start_index开始到末尾的字符串。例如var=“Barry”,则${var:0}返回Barry,${var:2-4}返回Bar,这里的2-4,需要从字符串的尾部开始计算
${var:start_index:length} 默认返回从start_index开始的length个字符,可以为负数。例如var=“0123456789”,${var:2:5}返回23456,${var:5:-2}返回567,这里的-2代表剩余两个字符不要,${var:0-3:-1}返回567
${var:-newstring} 返回值分两种情况。1.如果var为空或者未定义,则返回newstring2.如果var不为空,则返回变量值
${var:=newstring} 返回值分两种情况。1.如果var为空或者未定义,则返回newstring,并把newstring赋值给var2.如果var不为空,则返回变量值
${var:?newstring} 返回值分两种情况。1.如果var为空或者未定义,则返回newstring写入标准错误流,本语句失败2.如果var不为空,则返回变量值
${var:+newstring} 返回值分两种情况。1.如果var不为空,则返回newstring2.如果var为空,则返回空值
${var#string} 返回从左边删除string后的字符串,尽可能短的去匹配。例如var=“http://127.0.0.1/index.php”,则${var#*/},返回”/127.0.0.1/index.php”
${var##string} 返回从左边删除string后的字符串,尽可能长的去匹配。例如var=“http://127.0.0.1/index.php”,则${var##*/},返回"index.php"
${var%string} 返回从右边删除string后的字符串,尽可能短的去匹配。例如var=“http://127.0.0.1/index.php”,则${var%/*},返回"http://127.0.0.1"
${var%%string} 返回从右边删除string后的字符串,尽可能长的去匹配。例如var=“http://127.0.0.1/index.php”,则${var%%/*},返回"http:"
${var/substring/string} 返回var中第一个substring被替换成newstring后的字符串。例如var=“08880”,则${var/0/Barry},返回Barry88880
${var//substring/string} 返回var中中所有substring被替换成newstring后的字符串。例如var=“08880”,则${var//0/Barry},返回Barry8888Barry
$(command) 返回command命令指令后所输出的结果,例如$(date)返回就是date命令执行后的输出,相当于反引号下的date
$((算术表达式)) 返回双括号内的运算结果,例如$((500+5*4))返回520,可以不需要两边加空格

这里举一个简单的例子

 1#!/bin/bash
 2var="Barry"
 3echo $var
 4echo ${var}
 5echo ${#var}
 6var1="0123456789abcdef"
 7echo ${var1:0}
 8echo ${var1:0-5}
 9echo ${var1:2:5}
10echo ${var1:5:-2}
11echo ${var1:0-3:-1}
12echo "================"
13var6="http://127.0.0.1/index.php"
14echo ${var6#*/}
15echo ${var6##*/}
16echo ${var6%/*}
17echo ${var6%%/*}
18echo "================"
19echo $(date)
20echo $((500+5*4))
21echo "================"
22var7="08880"
23echo ${var7/0/Barry}
24echo ${var7//0/Barry}
25echo "================"
26newstring="new"
27echo ${var2:-newstring}
28echo $var2
29echo ${var:-newstring}
30echo $var
31echo "================"
32echo ${var3:=newstring}
33echo $var3
34echo ${var:=newstring}
35echo $var
36echo "================"
37echo ${var4:+newstring}
38echo $var4
39echo ${var:+newstring}
40echo $var
41echo "================"
42echo ${var:?newstring}
43echo ${var5:?newstring}

4.6循环运算

Shell 中,主要的循环语句有三种:while 循环,until 循环和for 循环。除了上述的循环关键字,还有三个关键字。

  • break:跳出整个循环
  • exit:跳出脚本
  • continue:跳出本次循环,接着执行下一次循环

4.6.1 while循环

while循环的逻辑

1while [ condition ]
2do
3    dosomething
4done

或者是下面的写法

1while [ condition ]; do
2    dosomething
3done

举例说明

 1#!/bin/bash
 2
 3while [ -z $response ] || [ $response != 'yes' ]
 4do
 5    read -p 'Say yes : ' response
 6    if [[ $response == 0 ]]; then
 7        echo "shell exit"
 8        # 退出此shell脚本
 9        exit 0
10    fi
11done
12
13n=0
14# 创造一个死循环
15# 这样的写法也可以while :
16while [ 1 ]
17do
18  sleep 1
19  # 算法加法,不加双括号被认为是字符串
20  ((n++))
21  echo loop $n time
22  if [[ $n == 5 ]]; then
23    echo "this time need break"
24    # 跳出循环
25    break
26  elif [[ $n == 3 ]]; then
27    echo "need to continue"
28    # 直接跳到变量指向的下一个循环列表
29    continue
30  else
31    echo "just go"
32  fi
33done

4.6.2 until循环

它也可以实现循环,只不过逻辑和 while 循环正好相反。

 1#!/bin/bash
 2# 推荐的一种比较相等的方式[[ "$response"x == "yes"x ]]
 3until [[ "$response"x == "yes"x ]]
 4do
 5    read -p 'Say yes : ' response
 6    if [[ $response = 0 ]]; then
 7        echo "shell exit"
 8        # 退出此shell脚本
 9        exit 0
10    fi
11done
12
13n=0
14# 创造一个死循环until [ ]
15until [ ]
16do
17  sleep 1
18  # 算法加法,不加双括号被认为是字符串
19  ((n++))
20  echo loop $n time
21  if [[ $n == 5 ]]; then
22    echo "this time need break"
23    # 跳出循环
24    break
25  elif [[ $n == 3 ]]; then
26    echo "need to continue"
27    # 直接跳到变量指向的下一个循环列表
28    continue
29  else
30    echo "just go"
31  fi
32done

4.6.3 for循环

for 循环可以遍历一个“取值列表”,基本的逻辑如下

1for var in 'var1' 'var2' 'var3' ... 'varn'
2do
3    dosomething
4done

Shell 中的 for 循环和习惯的 for 循环方式略有不同。shell中的for循环更倾向于,遍历列表的形式,当然也是兼容我们的习惯写法。

1#!/bin/bash
2# $1 为shell脚本传入的第一个参数
3j=$1
4for ((i=1; i<=j; i++))
5do
6    touch file$i && echo file $i is ok
7done

这里遍历的列表可以是自定义,也可以是变量

 1#!/bin/bash
 2
 3# 列表可以是自定义
 4for animal in 'dog' 'cat' 'pig'
 5do
 6    echo "Animal being analyzed : $animal"
 7done
 8echo "==================="
 9# 列表可以是变量
10listfile=`ls`
11for file in $listfile
12do
13    echo "File found : $file"
14done
15echo "==================="
16
17# 列表可以是变量,可以直接省略直接通过反引号的方式写入
18for file1 in `ls`
19do
20    echo "File found : $file1"
21done
22echo "==================="
23
24# 列表可以是文件的集合
25for i in /boot/*
26do
27    echo "/boot/ dir list : $i"
28done

通常我们会用到一些数字,通过seq来获取循环列表

 1#!/bin/bash
 2# 如果是从1开始,可以简写成for i in `seq n`
 3for i in `seq 1 5`
 4do
 5    echo $i
 6done
 7echo "==================="
 8
 9for i in `seq 8`
10do
11    echo $i
12done
13echo "==================="
14
15# 从1开始,间隔为2
16for i in `seq 1 2 10`
17do
18    echo $i
19done
20echo "==================="
21
22# 从5开始,间隔为-1,倒着数数
23for i in $(seq 5 -1 1)
24do
25    echo  "$i";sleep 1
26done
27echo "==================="
28
29# 从0开始到10的集合
30for i in {0..10}; do
31    echo $i
32done

或者通过外部传入的参数当做循环的列表

1#!/bin/bash
2echo "there are $# arguments in this scripts"
3# 通过变量N用来计数
4N=1    
5for i in $@
6do
7    echo "\$$N is $i"
8    ((N++))
9done

4.7函数运算

我们可以把函数比作一个香肠制造机,在输入那一头你把猪装进去,输出那一头就出来香肠了。

函数的定义

1funname () {
2    action
3    [return int]
4}

或者是

1function funname {
2    action
3    [return int]
4}

注意事项:

  1. 如果选择第一种,函数名后面跟着的圆括号里不加任何参数。
  2. 可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
  3. 参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n**(0-255)**
  4. 函数的完整定义必须置于函数的调用之前。

4.7.1传递参数

 1#!/bin/bash
 2print_func_1 () {
 3    echo "Hello, I am a function"
 4}
 5
 6print_func_2 () {
 7    echo Hello $1
 8}
 9
10print_func_1
11# print_func_1是无参,所以加参数也是没有意义的
12print_func_1 Matthew
13print_func_2 Matthew
14print_func_2 Mark

它传递参数的方式其实很像给 Shell 脚本传递命令行参数。我们把参数直接置于函数名字后面,然后就像我们之前 Shell 脚本的参数那样:$1$2$3等等。

4.7.2返回值

 1#!/bin/bash
 2# 显示返回
 3print_func_3 () {
 4    echo Hello $1
 5    return 1
 6}
 7
 8# 显示返回
 9print_func_3_1 () {
10    echo Hello $1
11    return 255
12}
13
14# 显示返回,这个超过255之后,就会对返回的值取余(N-1)%255
15print_func_3_2 () {
16    echo Hello $1
17    return 258
18}
19
20
21#隐式返回
22print_func_4 () {
23    cat $1 | wc -l
24}
25
26print_func_3 $1
27echo res = $?
28echo "================"
29
30print_func_3_1 $1
31echo res = $?
32echo "================"
33
34print_func_3_2 $1
35echo res = $?
36echo "================"
37
38line_num=$(print_func_4 $1)
39echo The file $1 has $line_num lines

4.7.3全局变量和局部变量

要定义一个局部变量,我们只要在第一次给这个变量赋值时在变量名前加上关键字 local 即可。定义在函数体里面,可不加local关键字。

在函数中,如果全局变量和局部变量相等,会优先使用局部变量的赋值,也就是说局部变量会被改变。

 1#!/bin/bash
 2
 3local_global_func () {
 4    local var1='local'
 5    echo Inside function: var1 is $var1 : var2 is $var2
 6    # 这里的 var1 是函数中定义的局部变量
 7    # 这里的 var2 是函数外定义的全局变量
 8    var1='local_var1'   
 9    var2='local_var2'
10    echo function call: var1 is $var1 : var2 is $var2
11}
12
13var1='global_var1'
14var2='global_var2'
15
16echo Before function call: var1 is $var1 : var2 is $var2
17
18local_global_func
19
20echo After function call: var1 is $var1 : var2 is $var2

4.7.4 函数重载

把函数的名字取成与我们通常在命令行用的命令相同的名字。函数重载的作用会起到一种包装的作用,比如命令前的前处理和命令后的后处理等等

1#!/bin/bash
2ls () {
3    echo $1
4    command ls -lh
5}
6
7ls $1

4.8Shell进阶

通过一个简单的统计来对shell进行一个回顾和总结

首先把需要的素材,放到和脚本同目录的地方,脚本地址如下所示

下载完成后,开始做一个英语字典做统计,对各个字母出现进行排序。

 1#!/bin/bash
 2
 3# Verification of parameter
 4# 确认参数
 5if [ -z $1 ]
 6then
 7    echo "Please enter the file of dictionary !"
 8    exit 0
 9fi
10
11# Verification of file existence
12# 确认文件存在
13if [ ! -e $1 ]
14then
15    echo "Please make sure that the file of dictionary exists !"
16    exit 0
17fi
18
19# Definition of function
20# 函数定义
21count_charactor () {
22    for char in {a..z}
23    do
24        # 这里的加o代表一个单词可能出现多个同一字母,一个字符匹配到一个算一个,匹配到多个算多个
25        # tr含义是字符串替换,前面的a-z变为A-Z,代表的是小写转换为大写
26        echo "$char - `grep -io "$char" $1 | wc -l`" | tr /a-z/ /A-Z/ >> tmp.txt
27    done
28    # sort是排序,r为从大到小,n为数字排序,k指定根据哪几列进行排序,这里为第2列,t为指定分割符,这里是-
29    sort -rn -k 2 -t - tmp.txt
30    rm tmp.txt
31}
32
33# Use of function
34# 函数使用
35count_charactor $1

总结

总的来说,本文主要是介绍了关于文本编辑器Nano和Vim,并且Vim有三个模式,各个模式可以互相切换。通过Vim可以去自己写shell的脚本,通过基本的概念,最后通过一个例子对条件,循环,函数等进行一个回顾和总结。

猜你喜欢

linux1 基础学习

linux2 权限和管理

linux4 NDK交叉编译

参考

[1] 一生只画眉, Shell if 条件判断, 2018.

[2] aaron_agu, shell if [[ ]]和[ ]区别 || &&, 2016.

[3] dreamtdp, Linux Shell编程case语句, 2012.

[4] 也许明天, shell变量的替换, 2015.

[5] 运维自动化&云计算, shell脚本报错:": =: unary operator expected", 2017.

[6] 斯言甚善, shell中的for循环用法详解, 2017.

[7] 小白的进阶, shell脚本去重的几种方法, 2019.

[8] 嘉年华, Linux shell tr 命令详解, 2019.

[9] 悠悠i, Linux环境变量配置全攻略, 2019.

[10] ee230, shell 二维数组, 2015.