JB的Shell之旅-30分钟带你入门

本文深入浅出地介绍了Shell脚本的基础知识,包括变量、注释、字符串操作、数组处理、条件流程控制以及常见命令如ps、grep、awk、sed的使用技巧,适合初学者快速掌握。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

写这篇文章的目的很简单,因为爱并恨过;

前段时间要改个安卓产品的脚本,惊奇发现理论是用shell,虽然实现的功能不复杂,但如果对没了解过shell或懂皮毛的同学,改起来是相当痛苦(如jb),调试半天各种找文档,性价比太低了,恨不得用Python重构。。

虽然还是能完成想要的结果,但花费的时间远超一开始想的,目前shell给jb的感觉就如去年正则带来的噩梦一般,知道这玩意,会一丢丢,但是真正撸码改内容的时候,查半天文档,调试半天不如愿;

因此就有了该篇,对新同学而言,目的是让你快速了解shell,对于老同学而言,目的是提供常用的shell命令快速唤醒记忆,并且贴出例子来快速实验,大神请右上,谢谢~

此处手动@敏爷,因为敏爷也咨询了个shell的问题,建议都看看,巩固下;

shell是什么

shell是一个用C编写的程序,是用户使用Linux的桥梁;

shell和shell脚本的概念

而平常说的shell就是指shell脚本,是一种为shell编写的脚本程序;

而shell还有另一种概念,是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务;
Ken Thompson的sh是第一种Unix Shell,Windows Explorer是一个典型的图形界面Shell;

用途

  • 自动化常用的命令
  • 执行系统管理和故障排除
  • 执行简单的应用程序
  • 处理文本或文件

示例

本文中说的shell,大部分指shell脚本,那就来看示例吧,想知道shell脚本是什么:

#!/bin/sh
cd ~
mkdir jb_shell
cd jb_shell

for ((i=0; i<5; i++)); do
	touch jb_$i.txt
done
复制代码

示例解释:

  • 第1行:指定脚本解释器,这里是用/bin/sh做解释器
  • 第2行:切换到当前用户的home目录
  • 第3行:创建一个目录jb_shell
  • 第4行:切换到jb_shell目录
  • 第6行:循环条件,一共循环5次,每次循环i+1
  • 第7行:创建一个jb_0-4.txt文件
  • 第8行:循环体结束

mkdirtouch都是系统自带的程序,一般都在/bin或/usr/bin目录下;

fordodonesh脚本语言的关键字;

环境

shell编程跟java、Python编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了;

OS

当前主流的操作系统都支持shell编程,本文档所述的shell编程是指Linux下的shell,讲的基本都是POSIX标准下的功能,所以,也适用于Unix及BSD(如Mac OS);

Linux

Linux默认安装就带了shell解释器;

Mac OS

Mac OS不仅带了sh、bash这两个最基础的解释器,还内置了ksh、csh、zsh等不常用的解释器;

Windows上的模拟器

windows出厂时没有内置shell解释器,需要自行安装,为了同时能用grep, awk, curl等工具,最好装一个cygwin或者mingw来模拟linux环境。

脚本解释器

sh

即Bourne shell,POSIX(Portable Operating System Interface)标准的shell解释器,它的二进制文件路径通常是/bin/sh,由Bell Labs开发;

本文讲的是sh,如果你使用其它语言用作shell编程,请自行参考相应语言的文档。

bash

Bash是Bourne shell的替代品,属GNU Project,二进制文件路径通常是/bin/bash;
业界通常混用bash、sh、和shell,比如你会经常在招聘运维工程师的文案中见到:熟悉Linux Bash编程,精通Shell编程

在CentOS里,/bin/sh是一个指向/bin/bash的符号链接:

[root@centosraw ~]# ls -l /bin/*sh
-rwxr-xr-x. 1 root root 903272 Feb 22 05:09 /bin/bash
-rwxr-xr-x. 1 root root 106216 Oct 17  2012 /bin/dash
lrwxrwxrwx. 1 root root      4 Mar 22 10:22 /bin/sh -> bash
复制代码

但在Mac OS上不是,/bin/sh和/bin/bash是两个不同的文件,尽管它们的大小只相差100字节左右:

iMac:~ wuxiao$ ls -l /bin/*sh
-r-xr-xr-x  1 root  wheel  1371648  6 Nov 16:52 /bin/bash
-rwxr-xr-x  2 root  wheel   772992  6 Nov 16:52 /bin/csh
-r-xr-xr-x  1 root  wheel  2180736  6 Nov 16:52 /bin/ksh
-r-xr-xr-x  1 root  wheel  1371712  6 Nov 16:52 /bin/sh
-rwxr-xr-x  2 root  wheel   772992  6 Nov 16:52 /bin/tcsh
-rwxr-xr-x  1 root  wheel  1103984  6 Nov 16:52 /bin/zsh
复制代码

高级编程语言

理论上讲,只要一门语言提供了解释器(而不仅是编译器),这门语言就可以胜任脚本编程,常见的解释型语言都是可以用作脚本编程的,如:Perl、Tcl、Python、PHP、Ruby;

Perl是最老牌的脚本编程语言了,Python这些年也成了一些linux发行版的预置解释器;

编译型语言,只要有解释器,也可以用作脚本编程,如C shell是内置的(/bin/csh),Java有第三方解释器Jshell,Ada有收费的解释器AdaScript;

如下是一个PHP Shell Script示例(假设文件名叫test.php):

#!/usr/bin/php
<?php
for ($i=0; $i < 10; $i++)
        echo $i . "\n";
复制代码

执行:

/usr/bin/php test.php
复制代码

或者:

chmod +x test.php
./test.php
复制代码

第一个shell脚本

编写

打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行;

输入一些代码,第一行一般是这样:

#!/bin/bash
复制代码

#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行;

运行

运行Shell脚本有两种方法:

作为可执行程序

chmod +x test.sh
./test.sh
复制代码

注意,一定要写成./test.sh,而不是test.sh;
运行其它二进制的程序也一样,直接写test.sh,linux系统会去PATH里寻找有没有叫test.sh的,而只有/bin, /sbin, /usr/bin,/usr/sbin等在PATH里,你的当前目录通常不在PATH里;
所以写成test.sh是会找不到命令的,要用./test.sh告诉系统说,就在当前目录找;

通过这种方式运行bash脚本,第一行一定要写对,好让系统查找到正确的解释器;

这里会有疑问,#!/bin/bash可以不写吗?
答案是可以的,如果这个系统是使用/bin/bash作为解释器的话,就可以省去,或者使用解释器参数运行就不需要;

作为解释器参数 这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:

/bin/sh test.sh
bash test.sh
复制代码


这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

变量

定义变量

定义变量时,变量名不需要加美元符号($),如:

your_name="jb"  # 这种情况,双引号可加可不加
your_name="jb test" #如果有空格要用双引号
复制代码

注意,变量名和等号之间不能有空格,这里踩坑多次,切忌切忌,用其他语言的时候习惯空格了;

使用变量

使用一个定义过的变量,只要在变量名前面加美元符号即可,如:

your_name="jb"
echo $your_name
复制代码

echo ${your_name} 变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

for girl in 苍老师 前田 波多 大榄; do
	echo "I like ${girl}Style"
done
复制代码

如果不给girl变量加花括号,写成echo "I like ${girl}Style",解释器就会把$girlStyle当成一个变量(其值为空),代码执行结果就不是想要的效果了;

重定义变量

已定义的变量,可以被重新定义,如:

your_name="jb"
echo $your_name

your_name="jbtest"
echo $your_name
复制代码

这样写是合法的,但注意,第二次赋值的时候不能写$your_name="jbtest"使用变量的时候才加美元符

注释

#开头的行就是注释,会被解释器忽略;

多行注释

sh里没有多行注释,只能每一行加一个#号。就像这样:

#--------------------------------------------
#jb test 
复制代码

但是,如果有几十行的代码要临时注释,每一个都价格#号太麻烦了,那有没有骚操作了?

有的,就是利用:<<连个,那先来介绍这两个玩意:

:冒号是一个空命令,可以认为与shell的内建命令true相同,它的返回值是0;

在while循环中 while : 与 while true 的作用是等效的; 在 if/then 中可作为占位符;

if conditions
then:  #什么都不做
else
take action 
fi
复制代码

<< tag是将开始标记 tag 和结束标记 tag 之间的内容作为输入,tag不是关键字,可以随意替换,只是要前后统一即可;

那另外一种多行注释的方式如下:

:<<jbtest
echo "I am jb"
echo "I am jb"
echo "I am jb"
jbtest
复制代码

:冒号空命令,啥都不做,<<是将内容重定向输入,两个jbtest(可任意替换,不是关键字)之间的内容通过<<追加给冒号:,但是:冒号对它们啥都不做,就相当于没做任何处理和输出,就相当于注释了;

这操作,的确牛,但是会有兼容性问题,如注释的内容里面带有冒号的话,会报错;

字符串

字符串是shell编程中最常用最有用的数据类型,字符串可以用单引号,也可以用双引号,也可以不用引号

单引号

str='this is jb'
复制代码

单引号字符串的限制:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
  • 单引号字串中不能出现单引号(对单引号使用转义符后也不行)

双引号

your_name='jb'
str="Hello, I know your are \"$your_name\"! \n"
复制代码
  • 双引号里可以有变量
  • 双引号里可以出现转义字符

字符串操作

拼接字符串

your_name="jb"
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"

echo $greeting $greeting_1

输出:hello, jb ! hello, jb !
复制代码

获取字符串长度:

取字符串长度;

string="jb"
echo ${#string} 
expr length $string

两个都是输出4,但一般用第一种较多;
复制代码

提取字符串

取特定长度的字符串内容;

string="jb is a jb"
echo ${string:0:4} # 输出jb i,从第0个位置开始提取4个字符串;
echo ${string:4} # 输出s a jb,从第4个位置开始提取字符串
echo ${string:(-6):3}
# 输出s a,负数表示从右开始计数,从右侧数起第6个位置提取3个字符串;
复制代码

查找字符串位置

找出具体字符串位置;

str="abc"
expr index $str "ab"  # 输出:1
expr index $str "b"  # 输出:2
expr index $str "x"  # 输出:0,不存在则0
复制代码

截取子串

str="abbc,def,ghi,abcjkl"

命令意义&输出
echo${str#a*c}输出,def,ghi,abcjkl 一个井号(#) 表示从左边截取掉最短的匹配 (这里把abbc字串去掉)
echo ${str##a*c}输出jkl 两个井号(##) 表示从左边截取掉最长的匹配 (这里把abbc,def,ghi,abc字串去掉)
echo ${str#"a*c"}输出abbc,def,ghi,abcjkl 因为str中没有"a*c"子串
echo ${str##"a*c"}输出abbc,def,ghi,abcjkl 同理
echo ${str#d*f}输出abbc,def,ghi,abcjkl
echo ${str#*d*f}输出,ghi,abcjkl
echo ${str%a*l}输出abbc,def,ghi, 一个百分号(%)表示从右边截取最短的匹配
echo ${str%%b*l}输出a 两个百分号表示(%%)表示从右边截取最长的匹配
echo ${str%a*c}输出abbc,def,ghi,abcjkl

1个井号#带一个数字,所以截取最短,2个井号则截取最长; 1个百分号%只是从右侧开始算,同上;

也许上面有同学会有疑问echo ${str#d*f},而什么会显示全部解决,因为这种方式是从头开始的,是字符串匹配的,真不智能了,因此列出其他可以截取字符串的方式:

格式说明
${string: start :length}从 string 字符串的左边第 start 个字符开始,向右截取 length 个字符;
${string: start}从 string 字符串的左边第 start 个字符开始截取,直到最后;
${string: 0-start :length}从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符;
${string: 0-start}从 string 字符串的右边第 start 个字符开始截取,直到最后;
${string#*chars}从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符;
${string##*chars}从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符;
${string%*chars}从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 左边的所有字符;
${string%%*chars}从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 左边的所有字符;

字符串替换

str="apple, tree, apple tree"
echo ${str/apple/APPLE}
#替换第一次出现的apple,输出:APPLE, tree, apple tree

echo ${str//apple/APPLE}  
# 替换所有apple,输出:APPLE, tree, APPLE tree

echo ${str/#apple/APPLE}
# 如果字符串str以apple开头,则用APPLE替换它,输出:APPLE, tree, apple tree

echo ${str/%apple/APPLE}  
# 如果字符串str以apple结尾,则用APPLE替换它,输出:apple, tree, apple tree
复制代码

这里面也有明显的问题,就是如果想替换第二、第三个怎么办? 抱歉,是不行的,只能用sed,下面会提及到;

数组

数组中可以存放多个值。 Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小; 与大部分编程语言类似,数组元素的下标由0开始;

Shell 数组用括号来表示,元素用空格符号分割开,语法格式如下:

array_name=(value1 ... valuen)
arr_num=(1 2 3 4 5 )
arr_string=("abc" "edf" "sss")
复制代码

也可以使用下标来定义数组:

array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
复制代码

获取数组长度

下面两种方式均可  

${#arr_number[*]}

${#arr_number[@]}
复制代码

例子:

arr_number=(1 2 3 4 5)
echo "数组元素个数为: ${#arr_number[*]}"

echo "数组元素个数为: ${#arr_number[@]}"

输出:5
复制代码

读取某个下标的值

arr_number=(1 2 3 4 5)
echo "${#arr_number[2]}"

${数组名[下标]}
输出:1
复制代码

对某个下标进行赋值

这里分两种情况: 指定的下标是存在的 直接替换新的指定值;

arr_number[2]=100,数组被修改为(1 2 100 4 5)
复制代码

指定的下标是不存在的 那新的赋值会添加后数组的尾部;

arr_number[100]=100,数组被修改为(1 2 100 4 5 100)
复制代码

删除

清除某个元素:

unset arr_number[1]
# 这里清除下标为1的数组;
复制代码

清空整个数组:

unset arr_number
复制代码

分片访问

分片访问形式为:${数组名[@或*]:开始下标:结束下标}, 注意,不包括结束下标元素的值;

${arr_number[@]:1:4}
# 这里分片访问从下标为1开始,元素个数为4,输出的是2 3 4 5 
复制代码

数值替换

形式为:${数组名[@或*]/数值/新值}

${arr_number[@]/2/98}
#就是把2替换成98,输出:1 98 3 4 5
复制代码

数组遍历

for v in ${arr_number[@]}; do
    echo $v;
done
复制代码

传参

命令行参数

一般使用脚本,都需要向脚本传递参数,那shell获取参数的格式是$n, n代表的是数据,1为执行脚本的第一个参数,2为执行脚本的第二位参数;

echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";  
复制代码

执行脚本:

$ bash test.sh one two three
Shell 传递参数实例!
执行的文件名:test.sh
第一个参数为:one
第二个参数为:two
第三个参数为:three
复制代码

特殊参数

还有几个特殊字符,这边不展开说明,自行了解吧;

参数处理说明
$#传递到脚本的参数个数
$*以一个单字符串显示所有向脚本传递的参数。如"$*"用「"」括起来的情况、以"$1 2 …n"的形式输出所有参数。
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@与$*相同,但是使用时加引号,并在引号中返回每个参数。

如"$@"用「"」括起来的情况、以"$1" "2" … "n" 的形式输出所有参数。 $- |显示Shell使用的当前选项,与set命令功能相同。 $? |显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

处理选项

如果脚本传参有N个,但是又不想一个一个$1$2这样判断,那怎么办?

while [ -n "$1" ]
do 
	case "$1" in
	-jb) echo "jb";;
	-jb1) echo "jb1";;
	-jb2) echo "jb2";;
	-jb3) echo "jb3";;
	*) echo "over"	;;
	esac 
	shift
done
复制代码

运行的结果:

$ bash test.sh -jb -jb2 -jb3 -jb4
jb
jb2
jb3
over
复制代码

管道

在 Bash 中,管道符使用"丨"代表; 管道符也是用来连接多条命令的,如"命令1丨命令2";

  • 命令 1 的正确输出作为命令 2 的操作对象;
  • 命令 1 必须有正确输出,而命令 2 必须可以处理命令 1 的输出结果;
  • 命令 2 只能处理命令 1 的正确输出,而不能处理错误输出;

比如:

adb shell ps | grep com
复制代码

就会输出一堆喊出com的内容,也就是利用了管道符号;

条件判断

条件判断的作用 判断某需求是否满足;

返回类型

  • true,返回0
  • false,返回1

格式

  • test 判断式
  • [ 判断式 ] (常用)

按照文件类型进行判断

测试选项作用
-b 文件判断该文件是否存在,并且是块设备文件
-c 文件判断该文件是否存在,并且是字符设备文件
-d 文件判断该文件是否存在,并且是目录
-e 文件判断该文件是否存在
-f 文件判断该文件是否存在,并且是普通文件
-L 文件判断该文件是否存在,并且是符号链接文件
-p 文件判断该文件是否存在,并且是管道文件
-s 文件判断该文件是否存在,并且非空
-S 文件判断该文件是否存在,并且是套接字文件

利用 &&|| 判断文件是否是目录

$ [ -d test ] && echo 'yes' || echo 'no'
yes

$ [ -d test1 ] && echo 'yes' || echo 'no'
no
复制代码

按照文件权限进行判断

测试选项作用
-r 文件判断该文件是否存在,并且当前用户拥有读权限
-w 文件判断该文件是否存在,并且当前用户拥有写权限
-x 文件判断该文件是否存在,并且当前用户拥有执行权限
-u 文件判断该文件是否存在,并且拥有SUID权限
-g 文件判断该文件是否存在,并且拥有SGID权限
-k 文件判断该文件是否存在,并且拥有SBit权限

判断文件权限

$  [ -r test ] && echo yes || echo no
yes

$  [ -w test ] && echo yes || echo no
yes

$  [ -x test ] && echo yes || echo no
yes
复制代码

两个文件之间进行比较

测试选项作用
文件1 -nt 文件2判断文件1的修改时间是否比文件2的新
文件1 -ot 文件2判断文件1的修改时间是否比文件2的旧
文件1 -ef 文件2判断文件1和文件2的Inode号是否一致,可以理解为两个文件是否为同一个文件。这适用于判断硬链接很好的方法

比较两个文件的最后修改时间

  • 相隔1秒,创建file1和file2两个文件
$ touch jb1; sleep 5; touch jb22

$ ll jb*
-rw-r--r-- 1 jb 197121 0 12月 11 15:17 jb
-rw-r--r-- 1 jb 197121 0 12月 11 15:17 jb1
-rw-r--r-- 1 jb 197121 0 12月 11 15:17 jb22
复制代码
  • 比较两个文件的最后修改时间
jb@LAPTOP-2R0431R1 MINGW64 /c
$ [ jb1 -ot jb22 ] && echo yes || echo no
yes

jb@LAPTOP-2R0431R1 MINGW64 /c
$ [ jb1 -nt jb22 ] && echo yes || echo no
no

复制代码

两个整数之间比较

测试选项作用
整数1 -eq 整数2判断整数1是否等于整数2
整数1 -ne 整数2判断整数1是否不等于整数2
整数1 -gt 整数2判断整数1是否大于整数2
整数1 -lt 整数2判断整数1是否小于整数2
整数1 -ge 整数2判断整数1是否大于等于整数2
整数1 -le 整数2判断整数1是否小于等于整数2

字符串的判断

测试选项作用
-z 字符串判断字符串是否为空
-n 字符串判断字符串是否为非空
字符串1 == 字符串2判断字符串1和字符串2是否相等
字符串1 != 字符串2判断字符串1和字符串2是否不相等
  • 字符串1 == 字符串2:判断字符串1和字符串2是否相等
$ a=111
$ b=222
$ [ "$a" == "$b" ] && echo yes || echo no
no
复制代码

多重条件判断

测试选项作用
判断1 -a 判断2逻辑与,判断1 和 判断2都为真,最终结果才为真
判断1 -o 判断2逻辑或,判断1 和 判断2有一个为真,最终结果就为真
! 判断逻辑非,使原始的判断式取反

流程控制

if else

if

if condition
then
	command1 
	command2
	...
	commandN 
fi
复制代码

写成一行(适用于终端命令提示符):

if `ps -ef | grep ssh`;  then echo hello; fi
复制代码

末尾的fi就是if倒过来拼写,对成对出现的;

if else

if condition
then
	command1 
	command2
	...
	commandN
else
	command
fi
复制代码

if else-if else

if condition1
then
	command1
elif condition2
	command2
else
	commandN
fi
复制代码

for while

for

在上面的示例里看到过:

for var in item1 item2 ... itemN
do
	command1
	command2
	...
	commandN
done
复制代码

写成一行:

for var in item1 item2 ... itemN; do command1; command2… done;
复制代码

while

while循环用于不断执行一系列命令,也用于从输入文件中读取数据;

while condition
do
	command
done
复制代码

无限循环

while :
do
	command
done
复制代码

或者

while true
do
	command
done
复制代码

或者

for (( ; ; ))
复制代码

until

until 循环执行一系列命令直至条件为 true 时停止; until 循环与 while 循环在处理方式上刚好相反;

一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。

until condition
do
	command
done
复制代码

case

Shell case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。

case 值 in
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
esac
复制代码

需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号用两个分号表示break;

跳出循环

break

break命令允许跳出所有循环(终止执行后面的所有循环)。

continue

ontinue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

函数

shell可以自定义函数,然后在shell脚本中随意调用;

定义

shell函数定义的格式:

function 函数名 () {  
        to do..
        return -n  
}  
复制代码
  • 可以function 函数名()定义,也可以直接函数名()定义,不带任何参数;
  • return是可选,不加则将以最后一条命令运行结果作为返回值;

调用

function jb1 () {  #前面的function是声明一个函数 名字叫 jb1 ()
    echo "jb1"   # 执行操作,输出jb1
}  
  
function jb2 () {  
    echo "jb2"  
}  

jb3() {
	echo "jb3"
}


jb1    # 调用jb1函数
jb2
jb3
复制代码

带参数函数实例

function jb () {  
        echo 我的名字叫: $1  
}  
jb $1 
复制代码

运行:

$ bash test.sh hahhahha
我的名字叫: hahhahha
复制代码

使用函数实现菜单脚本

function CDAN(){  
cat << yankerp    
+------------------------------------------------+  
|                                                |  
|        _o0o_          1. 安装Nginx             |  
|        08880          2. 安装Apache            |  
|       88"."88         3. 安装MySQL             |  
|       (|-_-|)         4. 安装PHP               |  
|        0\=/0          5. 部署LNMP环境          |  
|      __/   \__        6. 安装zabbix监控        |  
|     ‘\   ///‘       7. 退出此管理程序         |  
|    / Linux一键 \      8. 关闭计算机            |  
|  ||    Server   ||    ======================   |    
|  \        ////         一键安装服务           |  
|   |||  i i i    |||               by Yankerp   |  
|   ___        ___      ======================   |  
|___‘.  /--.--\ .‘___                            |  
+------------------------------------------------+  
yankerp  
}  
CDAN  
  
LOG_DIR=/usr/local/src  
read -p "请您输入1-8任意数值:" NUM  
  
if [ ${#NUM} -ne 1 ]  
  then  
        echo "请您输入1|2|3|4|5|6|7|8"  
        exit 1  
fi  
  
expr $NUM + 1 &>/dev/null  
if [ "$?" -ne 0 ]  
   then  
        echo "请您输入数值!"  
        exit 1  
fi  
  
if [ "$NUM" -gt 8 ];then  
        echo "请您输入比8小的数值"  
        exit 1  
elif [ "$NUM" -eq 0 ];then  
        echo "请您输入比0大的数值"  
        exit 1  
fi  
######################  
function Nginx_DIR() {  
        yum install -y gcc gcc-c++ pcre-devel zlib-devel openssl-devel &>/dev/null  
        if [ $? -eq 0 ]  
           then  
                cd $LOG_DIR  && wget http://nginx.org/download/nginx-1.12.2.tar.gz &>/dev/null && useradd -M -s /sbin/nologin nginx && \  
        tar zxf nginx-1.12.2.tar.gz && cd nginx-1.12.2/ && \  
                ./configure --prefix=/usr/local/nginx --with-http_dav_module --with-http_stub_status_module --with-http_addition_module --with-http_sub_module  --with-http_flv_module --with-http_mp4_module --with-pcre --with-http_ssl_module --with-http_gzip_static_module --user=nginx &>/dev/null && make &>/dev/null && make install &>/dev/null   
        fi  
  
        if [ -e /usr/local/nginx/sbin/nginx ];then  
                ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/ && nginx && echo "Nginx安装并启动成功!!!"  
        fi  
}  
  
if [ $NUM -eq 1 ]  
  then  
     echo "开始安装Nginx请稍等..." && Nginx_DIR  
fi
复制代码

输出:

select菜单

上面说了流程,这里就介绍一个比较特殊的流程控制:select

select的格式如下:

select name   [in   list ] 
do 
    循环体命令
done
复制代码

从格式上看,跟for的格式类似; select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示PS3 提示符,等待用户输入;

用户输入菜单列表中的某个选项,执行响应的命令,而输入的内容会被保存在内置变量REPLY 里;

select 是个无限循环,因此要记住用break 命令退出循环,或用exit 命令终止脚本。也可以按ctrl+c 退出循环。

例子:

text="Please select a number: "
select name in jb1 jb2 jb3 jb4
do
    case $name in
    jb1)
        echo "Hello, jb1."
        ;;
    jb2)
        echo "Hello,jb2."
        ;;
    jb3)
        echo "Hello, jb3."
        ;;
    jb4)
        echo "Hello, jb4."
        ;;
    *)
        echo "Sorry, there is no such person."
        ;;
    esac
done
复制代码

结果:

stdin&stdout&stderr

在Linux系统中,一切设备都看作文件。 而每打开一个文件,就有一个代表该打开文件的文件描述符。 程序启动时默认打开三个I/O设备文件:

  • 标准输入文件stdin,文件描述符0;
  • 标准输出文件stdout,文件描述符1;
  • 标准错误输出文件stderr,文件描述符2;
文件描述符缩写描述
0STDIN标准输入
1STDOUT标准输出
2STDERR标准错误

默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。

如果希望 stderr 重定向到 file,可以这样写:

$ command 2 > file
复制代码

如果希望 stderr 追加到 file 文件末尾,可以这样写:

$ command 2 >> file
复制代码

2 表示标准错误文件(stderr)。

如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:

$ command > file 2>&1
复制代码

或者

$ command >> file 2>&1
复制代码

如果希望对 stdin 和 stdout 都重定向,可以这样写:

$ command < file1 >file2
复制代码

command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。

常用的命令

sh脚本结合系统命令便有了强大的威力,在字符处理领域,有grep、awk、sed三剑客,grep负责找出特定的行,awk能将行拆分成多个字段,sed则可以实现更新插入删除等写操作。

ps

ps命令用来列出系统中当前运行的那些进程,但ps 提供的hi进程的一次性的查看,它所提供的查看结果并不是动态连续的;

如果想对进程时间监控,应该用 top 工具。

对系统中进程进行监测控制,查看状态,内存,CPU的使用情况,使用命令:/bin/ps

  • ps :是显示瞬间进程的状态,并不动态连续;
  • top:如果想对进程运行时间监控,应该用 top 命令;

作用

ps 命令就是最基本同时也是非常强大的进程查看命令; 使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。

命令行格式

使用方式:ps [options] [--help] 说明:显示瞬间行程 (process) 的动态 参数:见下方

参数

命令含义
ps a显示现行终端机下的所有程序,包括其他用户的程序
ps -A显示所有进程
ps c列出程序时,显示每个程序真正的指令名称
ps -e此参数的效果和指定"A"参数相同
ps e列出程序时,显示每个程序所使用的环境变量
ps f用ASCII字符显示树状结构,表达程序间的相互关系
ps -H显示树状结构,表示程序间的相互关系
ps -N显示所有的程序,除了执行ps指令终端机下的程序之外
ps s采用程序信号的格式显示程序状况
ps S列出程序时,包括已中断的子程序资料
ps -t<编号>指定编号,并列出属于该程序的状况
ps u 以用户为主的格式来显示程序状况
ps x 显示所有程序,不以程序来区分

最常用的方法是ps -aux,然后再利用一个管道符号导向到grep去查找特定的进程,然后再对特定的进程进行操作。

ps -aux | grep com.jbtest
#这样就会把com.jbtest相关的进程信息全部列出;
复制代码

常用

基本PS使用

$ps
复制代码

名称含义
PID运行着的命令(CMD)的进程编号
TTY命令所运行的位置(终端)
TIME运行着的该命令所占用的CPU处理时间
CMD该进程所运行的命令

列出目前所有的正在内存当中的程序

$ ps -aux
复制代码

名称含义
USER用户名
UID用户ID(User ID)
PID进程ID(Process ID)
PPID父进程的进程ID(Parent Process id)
SID会话ID(Session id)
%CPU进程的cpu占用率
%MEM进程的内存占用率
VSZ进程所使用的虚存的大小(Virtual Size)
RSS进程使用的驻留集大小或者是实际内存的大小,Kbytes字节。
TTY与进程关联的终端(tty)
STAT进程的状态:进程状态使用字符表示的(STAT的状态码)
START进程启动时间和日期
TIME进程使用的总cpu时间
COMMAND正在执行的命令行命令

关于STAT,可以通过截图看到有很多字符,具体的含义如下:

名称含义
R运行 Runnable (on run queue) 正在运行或在运行队列中等待。
S睡眠 Sleeping 休眠中, 受阻, 在等待某个条件的形成或接受到信号。
I空闲 Idle
Z僵死 Zombie(a defunct process) 进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放。
D不可中断 Uninterruptible sleep (ususally IO) 收到信号不唤醒和不可运行, 进程必须等待直到有中断发生。
T终止 Terminate 进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行。
P等待交换页
W无驻留页 has no resident pages 没有足够的记忆体分页可分配。
X死掉的进程
<高优先级进程 高优先序的进程
N低优先 级进程 低优先序的进程
L内存锁页 Lock 有记忆体分页分配并缩在记忆体内
s进程的领导者(在它之下有子进程);
l多进程的(使用 CLONE_THREAD, 类似 NPTL pthreads)
+位于后台的进程组

可以用 | 管道和 more 连接起来分页查看

ps -aux |more
复制代码

可以用 | 管道和 grep 过滤

ps -aux |grep ps
复制代码

把所有进程显示出来,并输出到jb.txt文件

ps -aux > jb.txt
复制代码

输出指定的字段

ps -o pid,ppid,pgrp,session,tpgid,comm
复制代码

根据 CPU 使用来升序排序

ps -aux --sort -pcpu | less
复制代码

根据 内存使用 来升序排序

ps -aux --sort -pmem | less
复制代码

树形显示进程

pstree
复制代码

通过进程名和PID过滤 使用 -C 参数,后面跟你要找的进程的名字。比如想显示一个名为getty的进程的信息,就可以使用下面的命令:

ps -C getty
复制代码

显示所有进程信息,连同命令行

ps -ef
复制代码

grep

grep命令用于查找文件里符合条件的字符串;

一般是结合管道|来使用,当然也支持单独带参使用;

grep 更适合单纯的查找或匹配文本

语法

grep [-abcEFGhHilLnqrsvVwxy][-A<显示列数>][-B<显示列数>][-C<显示列数>][-d<进行动作>][-e<范本样式>][-f<范本文件>][--help][范本样式][文件或目录...]
复制代码

参数说明

参数含义
-a 或 --text不要忽略二进制的数据。
-A<显示行数> 或 --after-context=<显示行数>除了显示符合范本样式的那一列之外,并显示该行之后的内容。
-b 或 --byte-offset在显示符合样式的那一行之前,标示出该行第一个字符的编号。
-B<显示行数> 或 --before-context=<显示行数>除了显示符合样式的那一行之外,并显示该行之前的内容。
-c 或 --count计算符合样式的列数。
-C<显示行数> 或 --context=<显示行数>或-<显示行数>除了显示符合样式的那一行之外,并显示该行之前后的内容。
-d <动作> 或 --directories=<动作>当指定要查找的是目录而非文件时,必须使用这项参数,否则grep指令将回报信息并停止动作。
-e<范本样式> 或 --regexp=<范本样式>指定字符串做为查找文件内容的样式。
-E 或 --extended-regexp将样式为延伸的普通表示法来使用。
-f<规则文件> 或 --file=<规则文件>指定规则文件,其内容含有一个或多个规则样式,让grep查找符合规则条件的文件内容,格式为每行一个规则样式。
-F 或 --fixed-regexp将样式视为固定字符串的列表。
-G 或 --basic-regexp将样式视为普通的表示法来使用。
-h 或 --no-filename在显示符合样式的那一行之前,不标示该行所属的文件名称。
-H 或 --with-filename在显示符合样式的那一行之前,表示该行所属的文件名称。
-i 或 --ignore-case忽略字符大小写的差别。
-l 或 --file-with-matches列出文件内容符合指定的样式的文件名称。
-L 或 --files-without-match列出文件内容不符合指定的样式的文件名称。
-n 或 --line-number在显示符合样式的那一行之前,标示出该行的列数编号。
-q 或 --quiet或--silent不显示任何信息。
-r 或 --recursive此参数的效果和指定"-d recurse"参数相同。
-s 或 --no-messages不显示错误信息。
-v 或 --revert-match显示不包含匹配文本的所有行。
-V 或 --version显示版本信息。
-w 或 --word-regexp只显示全字符合的列。
-x --line-regexp只显示全列符合的列。
-y此参数的效果和指定"-i"参数相同。

常用

上面说的很多参数,但一般来说,常用的非常少,那就来一起看看常用的命令吧;

可以用 | 管道和 grep 过滤 查找指定进程

ps -aux |grep ps
复制代码

在当前目录中,查找后缀有 jb 字样的文件中包含 jbtest 字符串的文件

grep jbtest *jb 
复制代码

上面这种用法,当目录下有文件夹,就会报错,grep: sound: Is a directory,因为grep默认只在当前目录,如果需要递归查找,则需要使用-r;

grep -r jbtest /etc/jb 
#这样就会输出jb目录下的所有文件包含jbtest的文件及内容
复制代码

从文件中读取关键词进行搜索

cat jb.txt | grep -f jb2.txt
#意思是根据grep的结果来进行对jb.txt的过滤

[root@localhost test]# cat jb.txt 
hnlinux
peida.cnblogs.com
ubuntu
ubuntu linux
redhat
Redhat
linuxmint
[root@localhost test]# cat jb2.txt 
linux
Redhat
[root@localhost test]# cat jb.txt | grep -f jb2.txt
hnlinux
ubuntu linux
Redhat
linuxmint
复制代码

显示包含ed或者at字符的内容行

cat jb.txt |grep -E "ed|at"
复制代码

正则匹配所有以字母 “b” 开头、字母 “t” 结尾的三个字符的单词

grep '\<b.t\>' jb.txt
复制代码

awk

awk是什么 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大;

简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理;

awk更适合格式化文本,对文本进行较复杂格式处理

语法

awk [选项参数] 'script' var=value file(s)
or
awk [选项参数] -f scriptfile var=value file(s)
复制代码
字符含义
F fs表示指定输入文件分隔符,fs是一个字符串或者是一个正则表达式,如-F;
-v var=value表示赋值一个用户定义变量;
-f scriptfile从脚本文件中读取awk命令;
-W compact在兼容模式下运行awk;
-W copyleft打印简短的版权信息;
-W help打印全部awk选项和每个选项的简短说明;
-W lint打印不能向传统unix平台移植的结构的警告;
-W lint-old打印关于不能向传统unix平台移植的结构的警告;
-W posix打开兼容模式,但有以下限制,不识别:/x、函数关键字、func、换码序列以及当fs是一个空格时,将新行作为一个域分隔符;操作符**和**=不能代替^和^=;fflush无效。
-W re-interval允许间隔正则表达式的使用,如括号表达式[[:alpha:]];
-W source program-text使用program-text作为源代码;
-W version打印bug报告信息的版本;

看不懂?没关系,一起来看看怎么用吧~

基本用法

awk '{pattern + action}' {filenames}
# 注意,只能用单引号
复制代码
  • pattern:表示awk在数据中查找的内容;
  • action:表示是在找到的匹配内容时所执行的一系列指令;

这里来用一个例子展开说明,jb.txt:

Tom is a man who cheats and plays with women's feelings. 
He goes to work every day to fish for fish. 
He lives in a muddle through life.
复制代码

用法1

awk '{[pattern] action}' {filenames}   
# 行匹配语句 awk '' 只能用单引号
复制代码

例子:

awk '{print $1,$4}' jb.txt
# 每行按空格或TAB分割,输出文本中的1、4项

Tom man
He work
He a

# 格式化输出
awk '{printf "%-8s %-10s\n",$1,$4}' jb.txt

------------------------------------------
Tom      man
He       work
He       a
复制代码

用法二

awk -F  
#-F相当于内置变量FS, 指定分割字符
复制代码

例子:

awk -F, '{print $1,$2}'   jb.txt
-----------------------------------
Tom is a man who cheats and plays with women's feelings.
He goes to work every day to fish for fish.
He lives in a muddle through life.

#使用内建变量
awk 'BEGIN{FS=","} {print $1,$2}'     jb.txt
---------------------------------------------
Tom is a man who cheats and plays with women's feelings.
He goes to work every day to fish for fish.
He lives in a muddle through life.

#使用多个分隔符.先使用空格分割,然后对分割结果再使用","分割
awk -F '[ ,]'  '{print $1,$2,$5}'   jb.txt
--------------------------------------------------------
Tom is who
He goes every
He lives muddle
复制代码

用法三

awk -v  
# 设置变量
复制代码

例子:

awk -va=1 '{print $1,$1+a}' jb.txt
---------------------------------------------
Tom 1
He 1
He 1

#这里可能会有疑问,为什么变量+1=1?
原来是因为,将变量通过”+”连接运算,自动强制将字符串转为整型。
非数字变成0,发现第一个非数字字符,后面自动忽略。 


awk -va=1 -vb=s '{print $1,$1+a,$1b}' jb.txt
---------------------------------------------
Tom 1 Toms
He 1 Hes
He 1 Hes
复制代码

用法四

awk -f {awk脚本} {文件名}

例子:
awk -f jb.awk log.txt
复制代码

运算符

运算符描述
= += -= *= /= %= ^= **=赋值
?:C条件表达式
\|\|逻辑或
&&逻辑与
~ ~!匹配正则表达式和不匹配正则表达式
< <= > >= != ==关系运算符
空格连接
+ -加,减
* / %乘,除与求余
+ - !一元加,减和逻辑非
^ ***求幂
++ --增加或减少,作为前缀或后缀
$字段引用
in数组成员

jb.txt:

1Tom is a man who cheats and plays with women's feelings. 
3He goes to work every day to fish for fish. 
5He lives in a muddle through life.
复制代码

过滤第一列大于2的行

awk '$1>2' jb.txt
---------------------------------------
3He goes to work every day to fish for fish.
5He lives in a muddle through life.
复制代码

过滤第一列等于3的行

awk '$1==3 {print $1,$3}' jb.txt
复制代码

过滤第一列大于2并且第二列等于'Are'的行

awk '$1>2 && $2=="Are" {print $1,$2,$3}' jb.txt
复制代码

内建变量

变量描述
$n当前记录的第n个字段,字段间由FS分隔
$0完整的输入记录
ARGC命令行参数的数目
ARGIND命令行中当前文件的位置(从0开始算)
ARGV包含命令行参数的数组
CONVFMT数字转换格式(默认值为%.6g)ENVIRON环境变量关联数组
ERRNO最后一个系统错误的描述
FIELDWIDTHS字段宽度列表(用空格键分隔)
FILENAME当前文件名
FNR各文件分别计数的行号
FS字段分隔符(默认是任何空格)
IGNORECASE如果为真,则进行忽略大小写的匹配
NF一条记录的字段的数目
NR已经读出的记录数,就是行号,从1开始
OFMT数字的输出格式(默认值是%.6g)
OFS输出记录分隔符(输出换行符),输出时用指定的符号代替换行符
ORS输出记录分隔符(默认值是一个换行符)
RLENGTH由match函数所匹配的字符串的长度
RS记录分隔符(默认是一个换行符)
RSTART由match函数所匹配的字符串的第一个位置
SUBSEP数组下标分隔符(默认值是/034)
awk '{print $1,$2,$5}' OFS=" $ "  jb.txt
---------------------------------------
1Tom $ is $ who
3He $ goes $ every
5He $ lives $ muddle

awk '{print NR,FNR,$1,$2,$3}' jb.txt
---------------------------------------
1 1 1Tom is a
2 2 3He goes to
3 3 5He lives in
复制代码

正则

# 输出第1列包含 "He",并打印第1列与第2列
awk '$1 ~ /He/ {print $1,$2}' jb.txt
--------------------------------------
3He goes
5He lives

# 输出包含"is" 的行
awk '/is/ ' jb.txt
---------------------------------------------
1Tom is a man who cheats and plays with women's feelings.
3He goes to work every day to fish for fish.
复制代码

~ 表示模式开始,// 中是模式。

忽略大小写

awk 'BEGIN{IGNORECASE=1} /he/' jb.txt
---------------------------------------------
1Tom is a man who cheats and plays with women's feelings.
3He goes to work every day to fish for fish.
5He lives in a muddle through life.
复制代码

拆分文件

#是按第6例分隔文件,其中的NR!=1表示不处理表头
awk 'NR!=1{print > $6}' jb.txt

#指定的列输出到文件
awk 'NR!=1{print $4,$5 > $6}' jb.txt
复制代码

awk脚本

上面演示的例子,会看到BEGIN跟END的关键字,这里来介绍下:

  • BEGIN{ 这里面放的是执行前的语句 }
  • END {这里面放的是处理完所有的行后要执行的语句 }

jb.txt:

Marry   2143 78 84 77
Jack    2321 66 78 45
Tom     2122 48 77 71
Mike    2537 87 97 95
Bob     2415 40 57 62
复制代码

test.sh:

#!/bin/awk -f
#运行前
BEGIN {
    math = 0
    english = 0
    computer = 0
 
    printf "NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL\n"
    printf "---------------------------------------------\n"
}
#运行中
{
    math+=$3
    english+=$4
    computer+=$5
    printf "%-6s %-6s %4d %8d %8d %8d\n", $1, $2, $3,$4,$5, $3+$4+$5
}
#运行后
END {
    printf "---------------------------------------------\n"
    printf "  TOTAL:%10d %8d %8d \n", math, english, computer
    printf "AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR
}
复制代码

运行结果:

awk -f test.sh jb.txt

NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL
---------------------------------------------
Marry  2143     78       84       77      239
Jack   2321     66       78       45      189
Tom    2122     48       77       71      196
Mike   2537     87       97       95      279
Bob    2415     40       57       62      159
---------------------------------------------
  TOTAL:       319      393      350
AVERAGE:     63.80    78.60    70.00
复制代码
工作流程
awk 'BEGIN{ commands } pattern{ commands } END{ commands }'
复制代码
  • 第一步:执行BEGIN{ commands }语句块中的语句;
  • 第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ commands }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。
  • 第三步:当读至输入流末尾时,执行END{ commands }语句块。

更多有关awk详细的内容,请跳转到runoobgnu查看,谢谢;

sed

sed是什么

sed是一款流编辑工具,用来对文本进行过滤与替换工作; sed通过输入读取文件内容,但一次仅读取一行内容进行某些指令处理后输出,sed更适合于处理大数据文件;

工作原理

sed在处理文本文件的时候,会在内存上创建一个模式空间,然后把这个文件的每一行调入模式空间用相应的命令处理,处理完输出;接着处理下一行,直到最后;

与vim的区别

vim需要通知处理文件的哪几行才会去处理,sed默认会处理文件的所有行,除非你告诉它不处理哪几行;

语法

sed [options] 'command' file(s)
sed [options] -f scriptfile file(s)
复制代码

options

参数含义
-e<script>或--expression=<script>以选项中的指定的script来处理输入的文本文件;
-f<script文件>或--file=<script文件>以选项中指定的script文件来处理输入的文本文件;
-h或--help显示帮助;
-n或--quiet或——silent仅显示script处理后的结果;
-V或--version显示版本信息;

参数 指定待处理的文本文件列表;

命令

参数含义
a在当前行下面插入文本;
i在当前行上面插入文本;
c把选定的行改为新的文本;
d删除,删除选择的行;
D删除模板块的第一行;
s替换指定字符;
h拷贝模板块的内容到内存中的缓冲区;
H追加模板块的内容到内存中的缓冲区;
g获得内存缓冲区的内容,并替代当前模板块中的文本;
G获得内存缓冲区的内容,并追加到当前模板块文本的后面;
l字符的清单;
n读取下一个输入行,用下一个命令处理新的行而不是用第一个命令;
N追加下一个输入行到模板块后面并在二者间嵌入一个新行,改变当前行号码;
p打印模板块的行;
P(大写)打印模板块的第一行;
q退出Sed;
b lable分支到脚本中带有标记的地方,如果分支不存在则分支到脚本的末尾;
r file从file中读行;
t labelif分支,从最后一行开始,条件一旦满足或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾;
T label错误分支,从最后一行开始,一旦发生错误或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾;
w file写并追加模板块到file末尾;
W file写并追加模板块的第一行到file末尾;
!表示后面的命令对所有没有被选定的行发生作用;
=打印当前行号码;
#把注释扩展到下一个换行符以前;

替换标记

参数含义
g表示行内全面替;
p表示打印行;
w表示把行写入一个文件;
x表示互换模板块中的文本和缓冲区中的文本;
y表示把一个字符翻译为另外的字符(但是不用于正则表达式);
\1子串匹配标记;
&已匹配字符串标记;

元字符集

参数含义
^匹配行开始,如:/^sed/匹配所有以sed开头的行;
$匹配行结束,如:/sed$/匹配所有以sed结尾的行;
.匹配一个非换行符的任意字符,如:/s.d/匹配s后接一个任意字符,最后是d;
*匹配0个或多个字符,如:/*sed/匹配所有模板是一个或多个空格后紧跟sed的行;
[]匹配一个指定范围内的字符,如/[ss]ed/匹配sed和Sed;
[^]匹配一个不在指定范围内的字符,如:/[^A-RT-Z]ed/匹配不包含A-R和T-Z的一个字母开头,紧跟ed的行;
\(..\)匹配子串,保存匹配的字符,如s/(love)able/\1rs,loveable被替换成lovers;
&保存搜索字符用来替换其他字符,如s/love/&/,love这成love
\<匹配单词的开始,如:/<love/匹配包含以love开头的单词的行。
\>匹配单词的结束,如/love>/匹配包含以love结尾的单词的行;
x\{m\}重复字符x,m次,如:/0{5}/匹配包含5个0的行;
x\{m,\}重复字符x,至少m次,如:/0{5,}/匹配至少有5个0的行;
x\{m,n\}重复字符x,至少m次,不多于n次,如:/0{5,10}/匹配5~10个0的行;

看到这里,是否懵逼了?

别看了,直接看实战吧;

实战

jb.txt的内容:

jb testing now 
复制代码

替换文本中的字符串

sed 's/jb/jb2/' jb.txt
--------------------------
jb2 testing now
复制代码

-n选项和p命令一起使用表示只打印那些发生替换的行:

sed -n 's/jb/jb2/p' jb.txt
复制代码

直接编辑文件选项-i,会匹配file文件中每一行的第一个jb替换为jb2:

sed -i 's/jb/jb2/g' jb.txt
复制代码

插入
方法1:
用sed的i命令在第一行前面插入即可,加上 -i 选项直接操作文件。
如果不加只是打印不会写入文件。

例如,文件头部添加一行字符:

sed -i '1i\3a0000' jb.txt
复制代码

方法2:
使用-e和-i选项;
jb.txt指定行(比如第三行)后面添加一行内容,比如“3a0000”:

sed -e "/3/a 3a0000" -i jb.txt

例子:

sed -i '2iabc' jb.txt 
复制代码

在jb.txt文件的第2行的上面插入abc,abc所行为第2行,原来行变成了第3行!

注意在行数后面的字母i!!!

sed -i '2aabc' jb.txt 
复制代码

jb.txt文件的第2行的下面插入abc,abc所在行为第3行,原来行不变任然为第2行。

注意在行数后面的字母a!!

在a.txt的第88行插入文件b.txt

sed -i '88 r b.file' a.file 

#如果不知道行号,可以用正則匹配
sed -i '/regex/ r b.txt' a.txt # regex是正则表达式
复制代码

如果不改变源文件,可以去掉-i开关,修改会输出到STDOUT!!

全面替换标记g
使用后缀 /g 标记会替换每一行中的所有匹配:

sed 's/jb/jb2/g' jb.txt
复制代码

当需要从第N处匹配开始替换时,可以使用 /Ng:

$ echo jbjbjbjb | sed 's/jb/jbtest/2g'
jbjbtestjbtestjbtest
复制代码

定界符
以上命令中字符 / 在sed中作为定界符使用,也可以使用任意的定界符:

sed 's:test:TEXT:g'
sed 's|test|TEXT|g'
复制代码

定界符出现在样式内部时,需要进行转义:

sed 's/\/bin/\/usr\/local\/bin/g'
复制代码

一般来说,/ :这种常用的符号都需要转义;

删除操作:d命令

删除空白行:

sed '/^$/d' jb.txt
复制代码

删除文件的第2行:

sed '2d' jb.txt
复制代码

删除文件的第2行到末尾所有行:

sed '2,$d' jb.txt
复制代码

删除文件最后一行:

sed '$d' jb.txt
复制代码

删除文件中所有开头是test的行:

sed '/^test/'d jb.txt
复制代码

已匹配字符串标记&
正则表达式 \w+ 匹配每一个单词,使用 [&] 替换它,& 对应于之前所匹配到的单词:

echo this is a test line | sed 's/\w\+/[&]/g'
---------------------------------------------
[this] [is] [a] [test] [line]
复制代码

所有以192.168.0.1开头的行都会被替换成它自已加localhost:

sed 's/^192.168.0.1/&localhost/' jb.txt
192.168.0.1localhost
复制代码

子串匹配标记\1
匹配给定样式的其中一部分:

echo this is digit 7 in a number | sed 's/digit \([0-9]\)/\1/'
----------------------------------------------------
this is 7 in a number
复制代码

命令中 digit 7,被替换成了 7。
样式匹配到的子串是 7,(..) 用于匹配子串,对于匹配到的第一个子串就标记为 \1,依此类推匹配到的第二个结果就是 \2,例如:

echo aaa BBB | sed 's/\([a-z]\+\) \([A-Z]\+\)/\2 \1/'
---------------------------------------------------------
BBB aaa
复制代码

love被标记为1,所有loveable会被替换成lovers,并打印出来:

sed -n 's/\(love\)able/\1rs/p' jb.txt
复制代码

组合多个表达式

sed '表达式' | sed '表达式'
等价于:
sed '表达式; 表达式'
复制代码

引用
sed表达式可以使用单引号来引用,但是如果表达式内部包含变量字符串,就需要使用双引号。

test=hello
echo hello WORLD | sed "s/$test/HELLO"
HELLO WORLD
复制代码

选定行的范围:,(逗号)
所有在模板test和check所确定的范围内的行都被打印:

sed -n '/test/,/check/p' jb.txt
复制代码

打印从第5行开始到第一个包含以test开始的行之间的所有行:

sed -n '5,/^test/p' jb.txt
复制代码

对于模板test和west之间的行,每行的末尾用字符串aaa bbb替换:

sed '/test/,/west/s/$/aaa bbb/' jb.txt
复制代码

多点编辑:e命令
·-e选项允许在同一行里执行多条命令:

sed -e '1,5d' -e 's/test/check/' jb.txt
复制代码

上面sed表达式的第一条命令删除1至5行, 第二条命令用check替换test。

命令的执行顺序对结果有影响。
如果两个命令都是替换命令,那么第一个替换命令将影响第二个替换命令的结果。

-e 等价的命令是 --expression

sed --expression='s/test/check/' --expression='/love/d' jb.txt
复制代码

从文件读入:r命令
file里的内容被读进来,显示在与test匹配的行后面,如果匹配多行,则file的内容将显示在所有匹配行的下面:

sed '/test/r file' filename
复制代码

写入文件:w命令
在example中所有包含test的行都被写入file里:

sed -n '/test/w file' example
复制代码

追加(行下):a命令
将 this is a test line 追加到 以test 开头的行后面:

sed '/^test/a\this is a test line' jb.txt
复制代码

在 test.conf 文件第2行之后插入 this is a test line:

sed -i '2a\this is a test line' test.conf
复制代码

插入(行上):i命令
将 this is a test line 追加到以test开头的行前面:

sed '/^test/i\this is a test line' jb.txt
复制代码

在test.conf文件第5行之前插入this is a test line:

sed -i '5i\this is a test line' test.conf
复制代码

下一个:n命令
如果test被匹配,则移动到匹配行的下一行,替换这一行的aa,变为bb,并打印该行,然后继续:

sed '/test/{ n; s/aa/bb/; }' jb.txt
复制代码

变形:y命令
把1~10行内所有abcde转变为大写,注意,正则表达式元字符不能使用这个命令:

sed '1,10y/abcde/ABCDE/' jb.txt
复制代码

退出:q命令
打印完第10行后,退出sed

sed '10q' jb.txt
复制代码

保持和获取:h命令和G命令
在sed处理文件的时候,每一行都被保存在一个叫模式空间的临时缓冲区中,除非行被删除或者输出被取消,否则所有被处理的行都将 打印在屏幕上。
接着模式空间被清空,并存入新的一行等待处理。

sed -e '/test/h' -e '$G' jb.txt
复制代码

在这个例子里,匹配test的行被找到后,将存入模式空间,h命令将其复制并存入一个称为保持缓存区的特殊缓冲区内。
第二条语句的意思是,当到达最后一行后,G命令取出保持缓冲区的行,然后把它放回模式空间中,且追加到现在已经存在于模式空间中的行的末尾。
在这个例子中就是追加到最后一行。
简单来说,任何包含test的行都被复制并追加到该文件的末尾。

保持和互换:h命令和x命令
互换模式空间和保持缓冲区的内容。
也就是把包含test与check的行互换:

sed -e '/test/h' -e '/check/x' file
复制代码

脚本scriptfile
sed脚本是一个sed的命令清单,启动Sed时以-f选项引导脚本文件名。
Sed对于脚本中输入的命令非常挑剔,在命令的末尾不能有任何空白或文本,如果在一行中有多个命令,要用分号分隔。
#开头的行为注释行,且不能跨行。

sed [options] -f scriptfile file(s)
复制代码

打印奇数行或偶数行
方法1:

sed -n 'p;n' test.txt  #奇数行
sed -n 'n;p' test.txt  #偶数行
复制代码

方法2:

sed -n '1~2p' test.txt  #奇数行
sed -n '2~2p' test.txt  #偶数行
复制代码

打印匹配字符串的下一行

grep -A 1 SCC URFILE
sed -n '/SCC/{n;p}' URFILE
awk '/SCC/{getline; print}' URFILE
复制代码

总的来说,sed 更适合编辑匹配到的文本

xargs

xargs是什么

xargs 是给命令传递参数的一个过滤器,也是组合多个命令的一个工具。

可以将输入内容(通常通过命令行管道传递),转成后续命令的参数,通常用途有:

  • 命令组合:尤其是一些命令不支持管道输入,比如ls。
  • 避免参数过长:xargs可以通过-nx来将参数分组,避免参数过长。

之所以能用到这个命令,关键是由于很多命令不支持|管道来传递参数,而日常工作中有有这个必要,所以就有了 xargs 命令:

find /sbin -perm +700 |ls -l       #这个命令是错误的
find /sbin -perm +700 |xargs ls -l   #这样才是正确的
复制代码

xargs 一般是和管道一起使用;

语法

somecommand |xargs -item  command
复制代码

参数

参数说明
-0当 stdin 含有特殊子元的时候,将其当成一般字符;
-a file从文件中读入作为sdtin;
-e flagflag必须是一个以空格分隔的标志,当xargs分析到含有flag这个标志的时候就停止;
-p当每次执行一个argument的时候询问一次用户;
-n num后面加次数,表示命令在执行的时候一次用的argument的个数,默认是用所有的;
-t表示先打印命令,然后再执行;
-i 或-I将xargs的每项名称,一般是一行一行赋值给 {},可以用 {} 代替;
-r no-run-if-empty当xargs的输入为空的时候则停止xargs,不用再去执行了;
-s num命令行的最大字符数,指的是 xargs 后面那个命令的最大命令行字符数;
-L num从标准输入一次读取 num 行送给 command 命令;
-d delim分隔符,默认的xargs分隔符是回车,argument的分隔符是空格,这里修改的是xargs的分隔符;
-xexit的意思,主要是配合-s使用;
-P修改最大的进程数,默认是1;

实战

定义一个测试文件,内有多行文本数据:

# cat test.txt

a b c d e f g
h i j k l m n
o p q
r s t
u v w x y z
复制代码

多行输入单行输出

# cat test.txt | xargs
---------------------------------------------------
a b c d e f g h i j k l m n o p q r s t u v w x y z
复制代码

-n 选项多行输出

# cat test.txt | xargs -n3
---------------------------------
a b c
d e f
g h i
j k l
m n o
p q r
s t u
v w x
y z
复制代码

-d 选项可以自定义一个定界符

# echo "nameXnameXnameXname" | xargs -dX
------------------------------------------
name name name name
复制代码

结合 -n 选项使用

# echo "nameXnameXnameXname" | xargs -dX -n2
------------------------------------------
name name
name name
复制代码

读取 stdin,将格式化后的参数传递给命令; 假设一个命令为 sk.sh 和一个保存参数的文件 arg.txt:

#!/bin/bash
#sk.sh命令内容,打印出所有参数。

echo $*
复制代码

arg.txt文件内容:

# cat arg.txt
------------------------------------------
aaa
bbb
ccc
复制代码

xargs 的一个选项 -I,使用 -I 指定一个替换字符串 {},这个字符串在 xargs 扩展时会被替换掉,当 -I 与 xargs 结合使用,每一个参数命令都会被执行一次:

# cat arg.txt | xargs -I {} ./sk.sh -p {} -l
-------------------------------------------
-p aaa -l
-p bbb -l
-p ccc -l
复制代码

复制所有图片文件到 /data/images 目录下

ls *.jpg | xargs -n1 -I cp {} /data/images
复制代码

xargs 结合 find 使用

用 rm 删除太多的文件时候,可能得到一个错误信息:/bin/rm Argument list too long. 用 xargs 去避免这个问题:

find . -type f -name "*.log" -print0 | xargs -0 rm -f
复制代码

xargs -0 将 \0 作为定界符; 统计一个源代码目录中所有 php 文件的行数

find . -type f -name "*.php" -print0 | xargs -0 wc -l
复制代码

查找所有的 jpg 文件,并且压缩它们

find . -type f -name "*.jpg" -print | xargs tar -czvf images.tar.gz
复制代码

xargs 其他应用

假如你有一个文件包含了很多你希望下载的 URL,你能够使用 xargs下载所有链接:

# cat url-list.txt | xargs wget -c
复制代码

curl

curl命令是个功能强大的网络工具,支持通过http、ftp等方式下载文件、上传文件。
还可以用来抓取网页、网络监控等方面的开发,解决开发过程中遇到的问题。

常用参数

curl命令参数很多,这里只列出曾经用过、特别是在shell脚本中用到过的那些。

参数说明
-v/--verbose小写的v参数,用于打印更多信息,包括发送的请求信息,这在调试脚本是特别有用;
-m/--max-time 指定处理的最大时长;
-H/--header 指定请求头参数;
-s/--slient减少输出的信息,比如进度;
--connect-timeout 指定尝试连接的最大时长;
-x/--proxy <proxyhost[:port]>指定代理服务器地址和端口,端口默认为1080;
-T/--upload-file 指定上传文件路径;
-o/--output 指定输出文件名称;
-d/--data/--data-ascii 指定POST的内容;
--retry 指定重试次数;
-e/--referer 指定引用地址;
-I/--head仅返回头部信息,使用HEAD请求;

实战

curl安装

sudo apt-get install curl
复制代码

GET请求

curl http://www.baidu.com
#回车之后,HTML内容打印在屏幕上;
#如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地;

curl -i "http://www.baidu.com"  
#显示全部信息

curl -l "http://www.baidu.com" 
#只显示头部信息

curl -v "http://www.baidu.com" 
#显示get请求全过程解析
复制代码

下载
保存网页:

curl -o baidu.html http://www.baidu.com
复制代码

下载一个图片

curl -o girl.jpg http://hostname.com/girl.jpg
复制代码

如果想下载图片的名字和服务器保持一致 -O 大写的O

curl -O http://hostname.com/girl.jpg
复制代码

可以看到屏幕上出现一个下载页面进度指示,等到100%,就保存完成了.

上传

-T/--upload-file:往服务器上传文件
复制代码

用法:

#上传多个文件
curl -T "img[1-1000].png" ftp://example.com/upload/

#上传多个文件
curl -T "{file1,file2}" http://www.example.com
复制代码

GET请求
带参:

curl http://www.xxxx.com/getDataList?param1=value1&param2=value2
复制代码

POST方法
-d或--data参数
post请求,用法:

curl -d "id=1&name=test" http://example.com/example.php
#需把请求的参数和URL分开
复制代码

同时可以使用:

curl -d "id=1" -d "name=test" http://example.com/example.php
#相当于提交了两个参数。
复制代码

也可以指定一个文件,将该文件中的内容当作数据传递给服务器端

curl --data @filename https://hostname.com/xxx
复制代码

当提交的参数值中有特殊字符就需要先转义。
如空格时,就需要转义成%20。

curl -d "value%201" http://hostname.com
复制代码

--data-urlencode参数
可以自动转义成特殊字符,无需人工事先转义。

curl --data-urlencode "name=April 1" http://example.com/example.php
复制代码

-F或--form
将本地文件上传到服务器,用法为:

curl -F "filename=@/home/test/test.pic" http://example.com/example.php 
#千万不能漏掉@符号。
复制代码

设置referer
有时候我们如果直接请求某个URL不能成功,它需要判断referer是否正确,那就可以通过-e或--referer参数模拟

curl --referer http://www.example.com http://www.example.com
复制代码

指定User Agent
-A/--user-agent
伪装成指定的浏览器Chrome访问

用法:

curl -A "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36" www.baidu.com
复制代码

伪造cookie
-b或--cookie:
有两种用法,一是指定参数和值:

curl --cookie "name=xxx" http://www.example.com 二是从文件读取:
curl -b /cookie.txt http://www.example.com
复制代码

保存cookie
-c/--cookie-jar
curl命令执行后保存操作时生成的cookie到文件:

curl -c ./cookie.txt -d username=aaaa -d pwd=****** http://www.example.com 
复制代码

将网站的cookies信息保存到sugarcookies文件中

curl -D cookies.txt http://localhost/sugarcrm/index.php
复制代码

定义输出显示内容
-w/--write-out:
可以定义输出的内容,如常用的http码,tcp连接时间,域名解析的时间,握手时间及第一时间响应时间等,非常强大。

1、打印出返回的http码

curl -o /dev/null -s -w %{http_code} "http://www.baidu.com" 
复制代码

2、打印响应时间

curl -o /dev/null -s -w "time_total: %{time_total}\n" "http://www.baidu.com"
复制代码

CURL授权
在访问需要授权的页面时,可通过-u选项提供用户名和密码进行授权

curl -u username:password URL
复制代码

通常的做法是在命令行只输入用户名,之后会提示输入密码,这样可以保证在查看历史记录时不会将密码泄露

curl -u username URL
复制代码

封装请求

#!/bin/bash
function httpRequest()
{
    #curl 请求
    info=`curl -s -m 10 --connect-timeout 10 -I $1`
 
    #获取返回码
    code=`echo $info|grep "HTTP"|awk '{print $2}'`
    #对响应码进行判断
    if [ "$code" == "200" ];then
        echo "请求成功,响应码是$code"
    else
        echo "请求失败,响应码是$code"
    fi
}
 
httpRequest "$1"
复制代码

执行结果:

小结

呼,终于搞定了,不容易,希望能帮助你快速了解shell;

本章从什么是shell开始,讲解变量、注释、字符串及操作、数组、传参、管道、条件/流程判断、以及常用的ps\grep\sed等命令简单介绍;

目的只是希望能快速了解shell,不然懵逼半天,简单看完后留个大概印象,好歹再次见面就知道什么了;

最后,谢谢大家~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值