文章目录
vim 程序编辑器
vi 分为 3 种模式:一般命令模式、编辑模式与命令行模式。
- 一般命令模式:用 vi 打开一个文件就直接进入一般命令模式,可以使用【上下左右】进行移动光标,也可以使用【删除字符】或【删除整行】来处理文件内容,也可以使用【复制、粘贴】处理文件内容。
- 编辑模式:按下【i、I、o、O、a、A、r、R】等任何一个字母之后进入编辑模式,使用【Esc】退出。
- 命令行模式:在一般命令模式中输入【: / ?】三个中的任何一个,就能将光标移动到最下面一行,可以提供【查找数据】或读取、保存、批量替换等操作。
常用的命令:
- 一般命令模式
- 光标移动
- h(向左移动光标)、j(向下移动光标)、k(向上移动光标)、l(向右移动光标)
- [Ctrl]+[f]:屏幕【向下】移动一页;[Ctrl]+[b]:屏幕【向上】移动一页。
- 0 或功能键[Home]:数字【0】移动到这一行的最前面字符处。
- $ 或功能键[End]:移动到这一行的最后面字符。
- G:移动到该文件最后一行。
- gg:移动到该文件第一行。
n<Enter>
:光标向下移动 n 行。
- 查找替换
- /word:向光标之下寻找 word 字符串。
- :n1,n2s/word1/word2/g:n1 和 n2 为数字,在第 n1 和 n2 行之间寻找 word1 字符串,然后替换为 word2,如
:100,200s/vbird/VBIRD/g
- 删除、复制与粘贴
- x 与 X:在一行中,x 为向后删除一个字符,X 为向前删除一个字符(相当于[Backspace])
- dd:删除(剪切)光标所在一整行,【ndd】删除光标所在向下 n 行。
- yy:复制光标所在的一行,【nyy】复制光标所在向下 n 行。
- p 和 P:p 粘贴在光标下一行,P 粘贴在光标上一行。
- u:恢复上一个操作。
- [Ctrl]+r:重做上一个操作。
- 【.】重复一个操作
- 编辑模式
- 进入插入或替换的编辑模式
- i 与 I:进入插入模式,i 为【从目前光标位置处插入】,I 为【在目前所在行第一个非空格符处开始插入】
- a 与 A:进入插入模式,a 为【从目前的光标所在下一个位置插入】,A 为【从光标所在行的最后一个字符处开始插入】
- o 与 O:进入插入模式,o 为【从目前光标下一行插入新的一行】,O 为【在目前光标上一行插入新的一行】
- r 与 R:进入替换模式,r 只会替换光标所在字符一次,R 会一只替换光标所在字符,直到按下【Esc】
- 命令行模式
- 保存与退出
- wq:保存并退出
- q!:强制退出,不保存
Bash
操作系统的内核(kernel)用来管理整个计算机硬件,一般用户需要通过 Shell 将输入的命令与内核沟通。
/bin/bash
是 Linux 默认的 shell,其主要优点有:
- 历史命令:按【上下键】就可以找到前后一个输入的命令,命令记录在
~/.bash_history
中。注意这里记录的是前一次登录以前执行过的命令,当前登录执行的命令都被缓存在内存中,只有在注销系统后才会记录到.bash_history
中。 - 命令与文件补全功能:【Tab】接在命令后则将命令补全,接在命令第二个字后面则为文件补齐。
- 命令别名设置:输入
alias lm='ls -al'
将命令【ls -al】替换为 lm。 - shell scripts:将平时管理系统常需要执行的连续命令写成文件。
使用命令 type
即可查看命令是否为 Bash shell 的内置命令。
Shell 的变量功能
变量区分大小写,可以使用 echo
命令来使用变量,但变量名前要加符号【$】才能访问其内容。
# echo $PATH
# echo ${PATH}
使用【=】对变量直接进行赋值操作,需要注意等号两边不能有空格,变量名称只能是英文和数字,且开头字符不能是数字。取消变量使用命令 unset
。
使用【read】将键盘中的输入赋值给一个变量,格式为:read [-pt] varible
,其中参数 -p
后面接提示字符,参数 -t
后面接等待的【秒数】。
双引号内的特殊字符如$
等,可以保持原本的特性,如 var="lang is $LANG"
显示为 lang is zh_CN>UTF-8
,而单引号内的特殊字符仅为一般字符(纯文本)。
环境变量
使用命令 export
可以将变量设置为环境变量。环境变量通常使用大写字母做名字。使用命令 env
可以列出所有的环境变量,使用命令 set
可以列出所有变量。
-
$HOME
:当前用户的家目录 -
$PATH
:以冒号分隔,表明命令的目录列表 -
$0
:当前 shell 的名字 -
$#
:传递给脚本的参数个数 -
$
本身也是个变量,代表目前这个 shell 的进程号,即PID,使用echo $$
出现的数字就是当前 shell 的 PID。
我们运行的一个 bash 就是一个独立的进程,那些在 bash 中执行的命令就被称为子进程,在原本的 bash 下面执行另一个 bash(就是子进程),原本的 bash 就会被暂停(sleep)。这里要注意子进程仅会继承父进程的环境变量。export
就是将变量变为环境变量,共享自己的变量设置给后来调用的文件或其他进程。
参数变量
如果脚本程序在调用时带有参数,一些额外的变量将被创建。即使没有传递任何参数,环境变量$#也依然存在,只不过它的值是0罢了。
$1, $2, $3, ...
:脚本程序的参数$*
:列出所有参数,各个参数之间使用环境变量IFS中的第一个字符分隔开$@
:是$*的一种精巧的变体,不使用IFS环境变量
变量的有效范围
环境变量可以被子进程所引用,而其他自定义变量内容就不会存在与子进程中。
- 当启动一个 shell,操作系统会分配一内存区域给 shell 使用,此内存中的变量可让子进程使用
- 若父进程利用 export 功能,让自定义变量的内容写到上述内存区域中(环境变量)
- 当加载另一个 shell 时,子 shell 可以将父 shell 的环境变量所在的内存区域导入自己的环境变量区块中。
Bash 的操作环境
环境配置文件让 bash 在启动时直接读取这些配置文件,以规划好 bash 的操作环境。
/etc/profile
:系统整体的设置,最好不要修改该文件。~/.bash_profile
或~/.bash_login
或 `~/.profile:属于用户个人设置
使用命令 stty -a
列出目前终端环境中所有的按键列表,使用命令 set
来显示或设置整个命令输出或输入的环境。
数据流重定向
当我们执行一个命令是,命令可能由文件读入数据,经处理后将数据输出到屏幕上。数据流重定向的功能就是将 standard output(stdout)与 standard error output(stderr)分别传送到其他的文件或设备,而分别传送所用的特殊字符如下:
- 标准输入(stdin):代码为 0,使用 < 或 <<;
- 标准输出(stdout):代码为 1,使用 > 或 >>;
- 标准错误输出(stderr):代码为 2,使用 2> 或 2>>;
例如输入命令 ll / > ~/rootflie
则屏幕不会显示命令 ll /
打印根目录文件的结果,其内容保存在文件 ~/rootfile
中。使用 >
会清空文件 ~/rootfile
的内容然后保存新的内容,而用 >>
则会将数据累加而不删除旧数据。
由此我们可以通过下面的命令将正确的结果和错误信息放在不同文件中:
# find /home -name .bashrc > list_right 2> list_error
如果要将错误信息忽略而只显示正确的数据,可以使用黑洞设备 /dev/null
,其可以吃掉任何导向这个设备的信息:
# find /home -name .bashrc 2> /dev/null
使用标准输入可以将文件内容替换键盘输入:
# cat > catfile < ~/.bashrc
文件 catfile
主动建立且内容和 ~/.bashrc
内容相同。而使用 <<
则代表【结束的输入字符】的意思,如用【cat】命令直接将输入的信息输出到 catfile
中,且当键盘输入 “eof” 时结束输入:
# cat > catfile << "eof"
> This is a test.
> Ok now stop
> eof
命令执行的判断依据
;
:如果一次执行多个指令,可以使用;
将命令隔开,或者使用 shell 脚本。$?
命令返回值:若前一个命令执行的结果为正确,在 Linux 下会返回一个$?=0
的值。- 与
&&
或||
:cmd1 && cmd2
如果 cmd1 正确执行则执行 cmd2,否则不执行。cmd1 || cmd2
如果 cmd1 正确执行则不执行 cmd2,否则执行。
管道命令(pipe)
管道命令使用【|】界定符号,例如:
# ls -al /etc | less
使用 less 来读取 ls 命令输出后的内容,即管道命令仅能处理经由前一个命令传来的正确信息,也就是标准输出的信息,对于标准错误并没有直接处理的能力。整体的管道命令如下所示:
管道后面接的第一个数据必定是【命令】,且这个命令必须能够接受标准输入的数据,这样的命令才是管道命令,例如 less、more、head、tail 等。
选取命令:cut、grep
-
cut
命令格式:
cut -d '分隔字符' -f fields <==用于有特定分隔字符 cut -c 字符区间 <==用于排列整齐的信息
选项与参数:
-d : 后面接分割字符,与 -f 一起使用; -f : 根据 -d 的分隔字符将一段信息划分为数段,用 -f 取出第几段; -c : 以字符的单位取出固定字符区间
如在变量 PATH 中以 “:” 为分隔,使用命令
echo $PATH | cut -d ':' -f 5
就可以得到以 ‘:’ 为分隔的第五个内容。此外我们还可以截取我们想要的信息。如我们去命令
export
的第 12 个字符以后的所有字符,使用命令explort | cut -c 12-
cut
主要就是将同一行里面的数据进行分解,最长用作分析一些数据或文字数据时。 -
grep
grep
是分析一行信息,若当中有我们需要的信息,就将该行拿出来,其命令格式如下:grep [-acinvAB] [--color=auto] '查找字符' filename
选项与参数:
- -a:将二进制文件以文本文件方式查找数据
- -c:计算找到“查找字符”的次数
- -i:忽略大小写
- -n:输出行号
- -v:反向选择,即显示没有“查找字符”的行
- -A:后面可加数字,为 after 的意思,除了列出该行外,后续 n 行也列出来
- -B:后面可加数字,为 before 的意思,除了列出该行外,前面的 n 行也列出来
注意默认的
grep
已经主动使用--color=auto
选项在alias
当中。grep 在数据中查询一个字符串时,是以【整行】为单位进行数据的选取。
双向重定位:tee
>
可以将数据流整个传给文件或设备,而 tee
命令可以同时将数据流分送到文件与屏幕,输出到屏幕的其实就是 stdout,就可以让下一个命令继续处理。
tee [-a] file
选项与参数:
-a : 已累加方式,将数据加入 file 当中
正则表达式
正则表达式就是处理字符串的方法,以行为单位来进行字符串的处理操作,正则表达式通过一些特殊符号的辅助,可以让用户轻易完成【查找、删除、替换】某特定字符串的处理过程。
基础正则表达式
使用正则表达式时,由于编码的不同,我们要留意当时环境的语系是什么,否则可能会发现与别人不相同的结果。
常用的特殊字符如下表:
字符 | 代表意义 |
---|---|
^ | 指向一行的开头 |
$ | 指向一行的结尾 |
. | 代表任意单个字符 |
[] | 方括号内包含一个字符范围,其中任何一个字符都可以被匹配 |
方括号中还可以使用一些有用的特殊匹配模式,如下表:
特殊符号 | 代表意义 |
---|---|
[:alnum:] | 代表英文大小写字符及数字,亦即0~9, A~Z, a~z |
[:alpha:] | 代表任何英文大小写字符,亦即A~Z, a~z |
[:blank:] | 代表空白键与[Tab] 按键两者 |
[:cntrl:] | 代表键盘上面的控制按键,亦即包括CR, LF, Tab, Del… 等等 |
[:digit:] | 代表数字而已,亦即0~9 |
[:graph:] | 除了空白字符(空白键与[Tab] 按键) 外的其他所有按键 |
[:lower:] | 代表小写字符,亦即a~z |
[:print:] | 代表任何可以被列印出来的字符 |
[:punct:] | 代表标点符号(punctuation symbol),亦即:" ’ ? ! ; : # $… |
[:upper:] | 代表大写字符,亦即A~Z |
[:space:] | 任何会产生空白的字符,包括空白键, [Tab], CR 等等 |
[:xdigit:] | 代表16 进位的数字类型,因此包括: 0-9, AF, af 的数字与字符 |
下面是一些常用的控制匹配的字符:
选项 | 代表意义 |
---|---|
? | 匹配是可选的,但最多匹配一次 |
* | 必须匹配 0 次或多次 |
+ | 必须匹配 1 次或多次 |
{n} | 必须匹配 n 次 |
{n,} | 必须匹配 n 次或 n 次以上 |
{n,m} | 匹配次数在 n 到 m 之间,包括 n 和 m |
这里要注意符号 { 和 } 在 shell 中有特殊意义,因此使用过程中要加转义字符,如
grep -n 'go\{2,\}g' regular_express.txt
格式化打印:printf
格式:printf '打印格式' 实际内容
格式字符串与C/C++中使用的非常相似,但有一些自己的限制,主要是不支持浮点数,因为shell中所有的数值运算都是按照整数来进行计算的。
文件比对:diff
diff
就是用在比对两个文件之间的差异,并且是以行为单位进行比对。diff 常用于同一文件的新旧版本差异。
格式:diff [-bBi] from-file to-file
选项与参数:
- -b:忽略一行当中,仅有多个空白的差异
- -B:忽略空白行的差异
- -i:忽略大小写的不同
Shell 脚本
shell 脚本就是利用 shell 功能所写的一个程序,这个程序是纯文本文件,将一些 shell 的语法与命令写在里面,搭配正则表达式、管道命令与数据流重定向等功能,以达到我们想要的处理目的。
建立一个简单的脚本,打印“Hello world!”:
#!/bin/bash
# Program:
# This program shows "Hello World!" in your screen.
# History:
# 2020/6/13 user First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Hello World! \a \n"
exit 0
建立良好的编写习惯,在文件头处记录好脚本的功能版本等信息,脚本运行时需要的环境变量预先声明与设置。
脚本执行方式的差异
当我们使用直接命令或使用 bash(或 sh)执行脚本时,脚本都会在一个新的 bash 环境来执行脚本内的命令。因此当子进程完成后,子进程内的各项变量或操作就会结束而不会传回给父进程中。
使用命令 source
就会让脚本在父进程中执行,因此要不注销系统而让某些写入 ~/.bashrc
的设置生效时,需要使用 source ~/.bashrc
。
条件判断式
利用 test 命令的测试功能
test -e filename
利用 test 命令可以查找 filename 文件是否存在。常用的选项和参数:
-e:该【文件名】是否存在
-f:该【文件名】是否存在且为文件
-d:该【文件名】是否存在且为目录
-r、-w、-x:检测该文件名是否具有【可读】、【可写】、【可执行】权限
利用判断符号[]
除了 test 外,还可以利用判断符号【[]】来进行数据的判断,例如想要知道 ${HOME}
这个变量是否为空,则:
[ -z "${HOME}" ]; echo $?
注意:中括号内个每个组件都要用空格分隔;中括号内的变量最好用双引号括起来;中括号内的常数最好用单或双引号括起来。一般中括号用在条件判断式 if...then..fi
的情况中。
利用 if…then 判断
格式:
if [ 条件判断式一 ]; then
当条件判断式一成立时,执行的命令
elif [ 条件判断式二 ]; then
当条件判断式二成立时,执行的命令
else
当条件判断式一和二均不成立,执行的命令
fi
elif 也是个判断式,elif 后面也要接 then 来处理。下面的脚本显示了用户选择:
#!/bin/bash
# Program:
# This program shows the user's choice
# History:
# 2020/6/14 First release
read -p "Please input (Y/N): " yn
if [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]; then
echo "OK, continue"
elif [ "${yn}" == "N" ] || [ "${yn}" == "n" ]; then
echo "Oh, interrupt!"
else
echo "I don't know what your choice is"
fi
使用 netstat
命令检查主机是否开启了指定的网络服务端口,即在每个服务的关键词后面都是接在冒号【:】后面,所以我们使用类似【:80】的方式来检测。
#!/bin/bash
# Program:
# Using netstat and grep to detect WWW, SSH, FTP and Mail services.
# History:
# 2020/6/14 Fan First release
PATH=/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
# 1. 先写一些告知的操作
echo "Now, I will detect your linux server's services!"
echo -e "The www, ftp, ssh and mail(smtp) will be detect! \n"
# 2. 开始进行一些测试的任务,并且输出一些信息
testfile=/dev/shm/netstat_checking.txt
netstat -tuln > ${testfile} # 先转存数据到内存中,不用一直执行 netstat
testing=$(grep ":80" ${testfile}) # 检查 80 端口是否存在
if [ "${testing}" != "" ]; then
echo "WWW is running in your system."
fi
testing=$(grep ":22" ${testfile}) # 检查 22 端口是否存在
if [ "${testing}" != "" ]; then
echo "SSH is running in your system."
fi
testing=$(grep ":21" ${testfile}) # 检查 21 端口是否存在
if [ "${testing}" != "" ]; then
echo "FTP is running in your system."
fi
testing=$(grep ":25" ${testfile}) # 检查 25 端口是否存在
if [ "${testing}" != "" ]; then
echo "Mail is running in your system."
fi
利用 case…case 判断
使用case结构可以通过一种比较复杂的方式将变量的内容和模式进行匹配,然后再根据匹配的不同模式去执行不同的代码。这要比使用if、elif和else语句来执行多个条件检查要简单的多。语法:
case $variable in
"第一个变量内容")
程序段
;;
"第二个变量内容")
程序段
;;
*) # 最后一个变量内容用 * 来表示所有其他值
不包含第一个变量内容和第二个变量内容的其他程序执行段
exit 1
;;
esac
注意:每个模式行都以双分号(;;)结尾,因为我们可以在前后模式之间放置多条语句,所以需要一个双分号来标记前一个语句的结束和后一个模式的开始。
下面的脚本让用户输入 one、two、three 并显示在屏幕上,如果参数不是则会提醒用户:
#!/bin/bash
# Program:
# This script only accepts the flowing parameter: one, two or three.
# History:
# 2020/6/15 Fan First release
PATH=/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "This program will print your selection!"
case ${1} in
"one")
echo "Your choice is ONE"
;;
"two")
echo "Your choice is TWO"
;;
"three")
echo "Your choice is THREE"
;;
*)
echo "Usage ${0} {one|two|three}"
;;
esac
利用 function 功能
【函数(function)】功能就是可以在 shell 脚本中做一个类似自定义执行命令的东西。shell 函数的结构如下:
function func_name()
{
statements
}
注意:shell 脚本的执行方式是由上而下、由左而右,因此 shell 脚本中的 function 的设置一定要在程序的最前面。
循环(loop)
while do done、until do done(不定循环)
如果需要重复执行一个命令序列,但事先又不知道这个命令序列应该执行的次数,那么我们通常需要一个while循环,它的语法如下所示:
while [ condition ]
do
程序段落
done
当 condition 条件成立时,就进行循环,直到 condition 的条件不成立才停止,还有一种不定循环的方式:
until [ condition ]
do
程序段落
done
until 和 while 正好相反,即当 condition 条件成立时就终止循环,否则就进行循环的程序段。下面是一个简单的密码验证程序:
#!/bin/bash
# Program:
# This program will verify your input code is right?
# History:
# 2020/6/15 Fan First release
PATH=/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "Please input your password: "
read passwd
while [ "${passwd}" != "yes" ]
do
read -p "Sorry, you input is wrong, please input right password: " passwd
done
echo "OK! you input the right password!"
exit 0
for do done(固定循环)
相比较 while、until 的循环方式,for 循环是已知循环的次数,语法是:
for variable in con1 con2 con3 ...
do
程序段
done
变量 $variable
每次循环的内容为:con1、con2、con3 …
用 ping 命令来检测 192.168.1.1 ~ 192.168.1.100 网段中的主机是否能与当前主机连通:
#!/bin/bash
# Program:
# History:
# Use ping command to check the network's PC state.
# 2020/6/15 Fan First release
PATH=/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
network="192.168.1" # 先定义一个域名前面部分
for sitenu in $(seq 1 100) # seq 为 sequence
do
# 下面程序获取 ping 的返回值是正确还是错误
ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1
# 开始显示结果是正确的启动还是错误的没有连通
if [ "${result}" == 0 ]; then
echo "Server ${network}.${sitenu} is UP."
else
echo "Server ${network}.${sitenu} is DOWN."
fi
done
除此之外,for 循环还有另一种写法:
for (( 初始值; 限制值; 赋值运算 ))
do
程序段
done
- 初始值:某个变量在循环中的起始值,类似 i=1
- 限制值:当变量的值在限制值的范围内,就继续循环
- 赋值运算:做一次循环变量变化
shell 脚本调试
sh [-nvx] scripts.sh
# -n:不执行脚本,仅查询语法问题
# -v:执行脚本前,将脚本文件的内容输出到屏幕上
# -x:将使用到的脚本内容显示到屏幕上
加号后面的数据为命令串,由于 sh -x 的方式将命令执行过程也显示出来,用户可以判断程序代码执行到哪一段时会出现相关的信息。