当前位置 : 首页 » 文章分类 :  开发  »  Linux-Shell脚本

Linux-Shell脚本

Linux Shell 笔记


Shell脚本概述

#! 解释器声明,是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell
例如 #!/bin/sh,shell 脚本通常以此开头,用来告诉系统 /bin/sh 是用来执行该文件的程序。

执行Shell脚本的两种方法

作为可执行程序

开头声明”#!/bin/sh”,将脚本保存为test.sh,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告诉系统说,就在当前目录找。

作为解释器参数

这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:
/bin/sh test.sh
/bin/php test.php
这种方式运行的脚本,不需要在第一行指定解释器信息。
如果在test.sh开头声明”#!/bin/sh”,则./test.sh 等价于 /bin/sh test.sh
其实,若不指定文件是什么格式,内核一律当作shell脚本,所以即使开头不写”#!/bin/sh”,加执行权限后也可以./tesh.sh执行成功


多个命令一起执行的方法

在命令行可以一次执行多个命令,有以下几种:

; 多个命令顺序执行结果不影响

1 每个命令之间用 ; 隔开
说明:各命令的执行结果,不会影响其它命令的执行。换句话说,各个命令都会执行,
但不保证每个命令都执行成功。
cd /home/PyTest/src; python suning.py

&& 前命令成功才执行后续命令

2 每个命令之间用 && 隔开
说明:若前面的命令执行成功,才会去执行后面的命令。这样可以保证所有的命令执行完毕后,执行过程都是成功的。
cd /home/PyTest/src && python suning.py

下面这种写法很巧妙,当 curl 执行成功时表示 etcd 进程已经存在了,就会执行后面的 echo 输出:

curl "http://localhost:2379" > /dev/null 2>&1 && echo "etcd is already running. Exiting."

|| 前命令成功则不执行后续命令

3、 每个命令之间用 || 隔开
说明:|| 是或的意思,只有前面的命令执行失败后才去执行下一条命令,直到执行成功
cd /home/masikkk/dir || echo "dir not exist"
dir 如果存在,后面的 echo 就不会执行。否则打印 dir not exist


Shell提示符$和#的区别

对于普通用户,Bash shell 默认的提示符是美元符号 $
对于超级用户(root 用户),Bash Shell 默认的提示符是井号 #
该符号表示 Shell 等待输入命令。

不同的 Linux 发行版使用的提示符格式不同。例如在 CentOS 中,默认的提示符格式为:
[user@localhost ~]$
这种格式包含了以下三个方面的信息:
启动 Shell 的用户名,也即 user
本地主机名称,也即 localhost;
当前目录,波浪号~是主目录的简写表示法。


shell初始化过程

shell的初始化过程是这样的:

  1. bash 检查文件 /etc/profile 是否存在, 如果存在,bash 就读取该文件,否则,跳过

  2. bash 检查当前用户 home 目录下的文件 .bash_profile 是否存在。 如果存在,bash 就读取該文件,否则,跳过

  3. bash 检查主目录下的 .bash_login 是否存在。 如果存在,bash 就读取该文件,否则,跳过

  4. bash 检查主目录下的文件 .profile 是否存在。如果存在,bash 就读取该文件,否则,跳过。

这些步骤都执行完后,就出现提示符了,非root用户默认提示符是 $, root用户默认提示符为#.


换行与注释

行尾添加 \ 进行换行
行首添加 # 进行注释

多行脚本与注释

换行后再加注释,会提示 command not found,所以下面的脚本是不行的

#!/bin/bash

# 启动myapp
nohup java -jar -Dtable.name=user myapp.jar \
#指定profile dev/qatest
--spring.profiles.active=dev \
# http端口
--server.port=8088 \

可以改为反引号包裹的方式实现注释,但其实也不太美观:

#!/bin/bash

# 启动myapp
nohup java -jar -Dtable.name=user myapp.jar \
`#指定profile dev/qatest`\
--spring.profiles.active=dev \
`# http端口`\
--server.port=8088 \

重定向

重定向命令列表

命令 说明
command > file 将标准输出重定向到 file,与 1>相同
command < file 将标准输入重定向到 file。
command >> file 将输出以追加的方式重定向到 file。
n > file 将文件描述符为 n 的文件重定向到 file。
n >> file 将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m 将输出文件 m 和 n 合并。
n <& m 将输入文件 m 和 n 合并。
<< tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入。

输出重定向

command1 > file1
上面这个命令执行command1然后将输出的内容存入file1。
执行后,并没有在终端输出信息,这是因为输出已被从默认的标准输出设备(终端)重定向到指定的文件。
重定向由shell处理,command1命令并不知道自己的输出有变化。
注意任何file1内的已经存在的内容将被新内容替代。如果要将新内容添加在文件末尾,请使用>>操作符。

>是重定向标准输出到文件,如果文件不存在,就创建文件;如果文件存在,就将其清空;
一般我们备份清理日志文件的时候,就是这种方法:先备份日志,再用>,将日志文件清空(文件大小变成0字节);
shell遇到”>”操作符,会判断右边文件是否存在,如果存在就先删除,并且创建新文件。不存在直接创建。 无论左边命令执行是否成功。右边文件都会变为空。
>>,这个是将输出内容追加到目标文件中。如果文件不存在,就创建文件;如果文件存在,则将新的内容追加到那个文件的末尾,该文件中的原有内容不受影响。

利用>/dev/null丢弃不需要的输出

利用 echo > xx.log 清空日志
或者 >xx.log 清空日志

输入重定向

command1 < file1
这样,本来需要从键盘获取输入的命令会转移到文件读取内容。

示例
统计 users 文件的行数
wc -l users
结果为:
2 users
也可以将输入重定向到 users 文件:
wc -l < users
结果为:
2
注意:上面两个例子的结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。
还有个例子:
more /vim/vimrc:输出有百分比,因为读的文件,知道文件长度
more < /vim/vimrc:输出无百分比,从重定向后的stdin读,不知道百分比

<< EOF 多行输入重定向

<< 是在 Linux Shell 中的一种特殊的重定向方式,它的基本的形式如下

cmd << delimiter
  Here Document Content line1
  Here Document Content line2
delimiter

它的作用就是将两个 delimiter 之间的内容(Here Document Content 部分) 传递给 cmd 作为输入参数,类似于起到一个临时文件的作用。
终止符(delimiter) 常用 EOF(End Of File),可任意自定义,在 linux 按 ctrl-d 就代表 EOF

1、比如在终端中输入 cat << eof ,系统会提示继续进行输入,输入多行信息再输入 eof,中间输入的信息将会显示在屏幕上。

# cat << eof
> line1
> line2
> eof
line1
line2

2、比如命令行输入邮件时,也可以用 << 输入多行内容到 mail 命令

#!/bin/sh
mail xxx@aaa.com << EOT
HOST xxx
DATE `date`
NOTE xxxx
EOT

3、将一个文件的内容输出到另一个文件中
cat fileA > fileB
或者
cat << EOF > fileB 执行后提示用户输入内容,多行输入结束后再输入 EOF,用户的输入内容被保存到了 fileB 中。

4、向文件 output.txt 里输入内容

# cat << EOF >output.txt
> 123123123
> 3452354345
> asdfasdfs
> EOF

追加

# cat << EOF >>output.txt
> 123123123
> 3452354345
> asdfasdfs
> EOF

或者

$ cat > users.json << EOF
{
  "vitess": [
    {
      "UserData": "vitess",
      "Password": "supersecretpassword"
    }
  ],
  "myuser1": [
    {
      "UserData": "myuser1",
      "Password": "password1"
    }
  ]}
EOF

同时输入输出重定向

command1 < infile > outfile
同时替换输入和输出,执行command1,从文件infile读取内容,然后将输出写入到outfile中。
例如
cat >catfile <test.sh
cat <test.sh >catfile
cat 从test.sh 获得输入数据,然后输出给文件catfile

stdin(0),stdout(1),stderr(2)

每个进程默认打开三个文件:stdin(0), stdout(1), stderr(2),0都指向终端输入,1,2指向终端输出。继承自同一进程组的shell进程,shell进程继承自开启时的getty进程。
每个进程默认(无参数时)从stdin输入,默认向stdout输出
终端操作模式为行缓冲模式时,需按下回车将内容输入进程。
从某终端登录后,执行的shell是此终端对应的会话组中的shell进程,所以不会显示到其他终端的输出中。

/dev/stdin -> /proc/self/fd/0
/dev/stdout -> /proc/self/fd/1
/dev/stderr -> /proc/self/fd/2

一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。重定向方式:<
  • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。重定向方式:> 或 1>
  • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。重定向方式:2>

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

> 为标准输出重定向,与 1> 相同
& 代表标准输出和标准错误输出,&> 表示同时重定向标准输出和标准错误
2>&1:将标准错误2重定向到标准输出1中的,此处1前面的&就是为了让bash将1解释成标准输出而不是文件1。试想2>1代表什么,2与>结合代表错误重定向,而1则代表错误重定向到一个文件1,而不代表标准输出;换成2>&1&与1结合就代表标准输出了,就变成错误重定向到标准输出
&[n]代表是已经存在的文件描述符,&1 代表输出,&2代表错误输出,
&-代表关闭与它绑定的描述符,>&-表示将标准输出关闭,<&-表示关闭标准输入(键盘),n>&-表示将n号输出关闭,n<&-表示将n号输入关闭

例如
ls -al 1>list.txt 2>list.err
将ls执行结果,正确的输出到 list.txt,错误的数据输出到 list.err

ls -al 1>list.txt 2>&1
将ls执行结果,不论正确或错误均输出到 list.txt 当中

ls -al 1>list.txt 2>&-
将错误输出信息关闭掉

ls -al >/dev/null 2>&1
将标准输出重定向到/dev/null,将标准错误转发到标准输出,所以也到/dev/null了

ls -al &>/dev/null
将所有标准输出与错误输出 输入到/dev/null

执行命令或shell脚本并将输出重定向到文件

ls xxx >out.txt 2>&1 实际上可换成 ls xxx 1>out.txt 2>&1 重定向符号 > 默认是1, 错误和输出都传到out.txt了。
为何 2>&1 要写在后面?
command > file 2>&1
首先是command > file将标准输出重定向到file中,2>&1 是标准错误拷贝了标准输出的行为,也就是同样被重定向到file中,最终结果就是标准输出和错误都被重定向到file中。
command 2>&1 >file
2>&1 标准错误拷贝了标准输出的行为,但此时标准输出还是在终端。>file 后输出才被重定向到file,但标准错误仍然保持在终端。


Shell变量

shell 变量全都是字符串

shell脚本与环境变量

Linux 是一个多用户的操作系统。每个用户登录系统后,都会有一个专用的运行环境。

用户登录到 Linux 系统后,系统将启动一个用户 shell。在这个 shell 中,可以使用 shell 命令或声明变量,也可以创建并运行 shell 脚本程序。
运行 shell 脚本程序时,系统将创建一个子 shell。此时,系统中将有两个 shell,一个是登录时系统启动的 shell,另一个是系统为运行脚本程序创建的 shell。 当一个脚本程序运行完毕,它的脚本 shell 将终止,可以返回到执行该脚本之前的 shell。
执行一个脚本时,会先开启一个子 shell 环境,然后将父 shell 中的所有系统环境变量复制过来,这个脚本中的语句就在子 shell 中执行。(也就是说父 shell 的环境变量在子 shell 中可以调用,但反过来就不行,如果在子 shell 中定义了环境变量,只对该 shell 或者它的子 shell 有效,当该子 shell 结束时,也可以理解为脚本执行完时,变量消失。

从这种意义上来说,用户可以有许多 shell,每个 shell 都是由某个 shell(称为父 shell)派生的。

在子 shell 中定义的变量只在该子 shell 内有效。如果在一个 shell 脚本程序中定义了一个变量,当该脚本程序运行时,这个定义的变量只是该脚本程序内的一个局部变量,其他的 shell 不能引用它。
要使某个变量的值可以在其他 shell 中被改变,可以使用 export 命令对已定义的变量进行输出。 export 命令将使系统在创建每一个新的 shell 时定义这个变量的一个拷贝。这个过程称之为变量输出。

1、执行脚本时是在一个子 shell 环境运行的,脚本执行完后该子 shell 自动退出;
2、一个 shell 中的系统环境变量(用 export 定义的变量)才会被复制到子 shell 中;
3、一个 shell 中的系统环境变量只对该 shell 或者它的子 shell 有效,该 shell 结束时变量消失 (并不能返回到父 shell 中)。
4、不用 export 定义的变量只对该 shell 有效,对子 shell 也是无效的。

env,set,export区别

set: 显示或设置 shell 变量, 包括用户环境变量和 shell 局部变量
env: 显示或设置环境变量, 只涉及当前用户的环境变量, 所以 env 看到的变量是 set 看到的变量的子集, 或者说 env 看到的变量个数小于等于 set 看到的变量个数
shell变量(set) = shell局部变量 + env环境变量
export: 显示当前导出成用户环境变量的 shell 变量,或将 shell 局部变量导出为用户环境变量

举例:

# aaa=1234567  #在当前shell定义变量
# echo $aaa
1234567
# env |grep aaa #环境变量中没有
# set |grep aaa #shell变量中有
aaa=1234567
# export |grep aaa #shell导出环境变量中也没有
# export aaa #导出为环境变量
# env |grep aaa #环境变量中有
aaa=1234567
# export| grep aaa #shell导出环境变量中有
declare -x aaa="1234567"

shell环境变量以及set,env,export的区别
https://blog.csdn.net/longxibendi/article/details/6125075

shell变量类别

我们通常所说的 shell 变量,是由用户的环境变量和shell局部变量共同组成的,这些变量保证了shell的正常运行
shell变量(set) = shell局部变量 + env环境变量

shell变量包括两种变量

shell局部变量

局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
通过赋值语句定义好的变量,可以通过如下方法定义shell变量

A1="1234"
delcare A2="2345"

用户环境变量

所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
通过export语法导出的shell私有变量,可以通过如下方法导出用户环境变量

A1="1234"
export A1  #先定义再导出
export A3="34"

环境变量
export:把普通变量变为环境变量,将变量从普通变量表拷贝到环境变量表中
unset xx,删除xx环境变量的定义
普通变量,环境变量的区别:子进程能否看得到。exec命令时,环境变量表会保留在新进程的内存中,但普通变量已被丢弃。
c语言中:getenv()获取环境变量,setenv()设置环境变量
shell所有变量默认都是全局变量。在函数中定义的变量也是全局的,函数外也能用。
关键字local用于定义局部变量。


定义变量

val="valtest"
注意,**变量名和等号之间不能有空格,如果写成val = "valtest"会报错val: command not found,因为linux中命令和参数间以空格分割,解释器会将val看做一个命令,后面的看做val的两个参数,PATH中找不到val命令,所以报错。

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

your_name="tom"
echo $your_name
your_name="alibaba"
echo $your_name

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

命令行中设置变量

有一种用法,在执行 shell 脚本前的命令行中修改变量,只针对这一次脚本执行生效,比如有如下 shell 脚本:

#! /bin/sh
echo $matt

执行 matt="masikkk.com" sh aa.sh 即在执行 aa.sh 的命令行前设置一个变量 matt,在脚本中就可以读取这个变量

# matt="masikkk.com" sh aa.sh
masikkk.com

这种用法在某些临时修改环境变量的场景下很有用,比如执行 jmeter 前修改 HEAP 环境变量 HEAP="-Xms512m -Xmx2048m" jmeter xxx jmeter 中会自动读 HEAP 来设置 jvm 堆内存大小。

unset删除变量

使用 unset 命令可以删除变量。语法:
unset variable_name
变量被删除后不能再次使用。unset 命令不能删除只读变量。


使用变量

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

your_name="matt"
echo $your_name
echo ${your_name}

变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,建议加上。


特殊变量

  • !!,连续两个英文叹号,表示执行上一条指令

  • $n,n为数字,$n为执行脚本的第一个参数,相当于main函数的argv[n]。例如,$0就是argv[0],即脚本程序自身的名称

  • $#,传递到脚本的参数个数,不包括脚本本身

  • $?:当前shell进程中,上一个命令的返回值,如果上一个命令成功执行则 $? 的值为0,否则为其他非零值,常用做 if 语句条件

  • $$:当前shell进程的pid

  • $!:后台运行的最后一个进程的pid

  • $-:显示Shell使用的当前选项,与set命令功能相同。

  • $_:之前命令的最后一个参数

可以修改自己的argv[0]来伪装成别的程序,在程序中用execl调用自己,调用时argv[0]设为其他命令。所以ps看时可能不准。
如何查看某pid xxx真正的命令?
/proc/xxx/下有个exe符号链接,指向此pid真正的命令。
readlink exe,读取符号链接

$*$@的区别

  • $*,以一个单字符串显示所有向脚本传递的参数,不包括$0$* 会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
  • $@,与$*类似,从$1开始的所有参数,但每个都作为独立的字符串
    相同点:都是引用所有参数。
    不同点:只有在双引号中体现出来。假设在脚本运行时有三个参数 myapp dev xx.log, 则"$*"等价于 “myapp dev xx.log”(单个字符串),而"$@"等价于 “myapp” “dev” “xx.log”(三个字符串)。
    $@ 外层建议都加上双引号,原因参考 shell 语法规范
    Double quote array expansions to avoid re-splitting elements.
    https://github.com/koalaman/shellcheck/wiki/SC2068

如果使用 echo 直接输出”$*”和”$@”做对比,是看不出区别的;但如果使用 for 循环来逐个输出数据,立即就能看出区别
创建 test.sh

#!/bin/bash
echo "print each param from \"\$*\""
for var in "$*"
do
    echo "$var"
done
echo "print each param from \"\$@\""
for var in "$@"
do
    echo "$var"
done

带参数执行结果:

$ . ./test.sh a b c d
print each param from "$*"
a b c d
print each param from "$@"
a
b
c
d

$* 只循环了一次,$@ 循环了4次


Shell字符串

字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟PHP类似。

单引号和双引号

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

双引号的优点:
双引号里可以有变量
双引号里可以出现转义字符;

例如

val=lalala
str1="string test $val"
str2="string test "$val""
str3="string test \"val\""
echo $str1
echo $str2
echo $str3

结果:
string test lalala
string test lalala
string test “val”


字符串拼接

#!/bin/bash
name="Shell"
str="Test"
str1=$name$str #中间不能有空格
str2="$name $str" #如果被双引号包围,那么中间可以有空格
str3=$name": "$str #中间可以出现别的字符串
str4="$name: $str" #这样写也可以
str5="${name}Script: ${str}" #这个时候需要给变量名加上大括号,否则无法区分出变量
echo $str1
echo $str2
echo $str3
echo $str4
echo $str5

运行结果:
ShellTest
Shell Test
Shell: Test
Shell: Test
ShellScript: Test


${}变量替换

删除左边字符

假设有变量 var=http://www.aaa.com/123.htm

#号截取,删除左边字符,保留右边字符
${var#*char}, var 表示要截取的字符变量, char 是指定的字符(或者子字符串),是通配符的一种,表示任意长度的字符串。char连起来使用的意思是:从左边开始直到遇见 char

echo ${var#*//}, // 表示从左边开始删除第一个 // 号及左边的所有字符
其中 var 是变量名即要处理的字符串,# 号是运算符,
// 表示从左边开始删除第一个 // 号及左边的所有字符
即删除 http://
结果是 :www.aaa.com/123.htm

url="http://c.biancheng.net/index.html"
echo ${url#*:} # 结果为 //c.biancheng.net/index.html

##号截取,删除左边字符,保留右边字符
echo ${var##*/}, ##*/ 表示从左边开始删除最后(最右边)一个 / 号及左边的所有字符
即删除 http://www.aaa.com/
结果是 123.htm

${var%}删除右边字符

假设有变量 var=http://www.aaa.com/123.htm
%号截取,删除右边字符,保留左边字符
echo ${var%/*}, %/* 表示从右边开始,删除第一个 / 号及右边的字符
%/* 表示从右边开始,删除第一个 / 号及右边的字符
结果是:http://www.aaa.com

%% 号截取,删除右边字符,保留左边字符
echo ${var%%/*}, %%/* 表示从右边开始,删除最后(最左边)一个 / 号及右边的字符
%%/* 表示从右边开始,删除最后(最左边)一个 / 号及右边的字符
结果是:http:


${var:start:len} 截取字符串

1、指定从左边第几个字符开始以及子串中字符的个数
${var:start:len} 从左边第 start+1 个字符开始的连续 len 个字符
例如:

$ a=welcome
$ echo $a
welcome
$ echo ${a:1:2}
el

2、从左边第几个字符开始一直到结束
${var:n} 从左边第 n+1 个字符到结束
例如

$ a=welcome
$ echo ${a:3}
come

3、从右边第几个字符开始以及字符的个数
${var:0-start:len}, 从右边起第 start 个字符开始的连续 len 个字符

$ a=welcome
$ echo ${a:0-4:2}
co

4、从右边第几个字符开始一直到结束
${var:0-start}, 从右边起第 start 个字符开始一直到结束

$ a=welcome
$ echo ${a:0-4}
come

${变量/查找值/替换值}字符串替换

${变量/查找值/替换值}
一个/表示替换第一个
两个//表示替换所有
当查找出中出现了一些需要转义的需要加上\:”/“需要转移成”/“,”#”需要转移成”#“

var=analy#analy.properties
echo ${var/#/=}
输出是:analy=analy.properties


字符串分割

字符串按,分隔

#!/bin/bash
string="hello,shell,haha"
array=(${string//,/ })
for var in ${array[@]}
do
echo $var
done

其中的 ${string//,/ } 是字符串替换命令,也就是将所有 , 替换为空格

获取字符串长度

$ str="adsa"
$ echo ${#str}
4

字符串是否包含子串

利用grep查找

1、先打印长字符串,然后在长字符串中 grep 查找要搜索的字符串,用变量 result 记录结果
如果结果不为空,说明 strA 包含 strB。如果结果为空,说明不包含。

strA="long string"
strB="string"
result=$(echo $strA | grep "${strB}")
if [[ "$result" != "" ]]
then
    echo "包含"
else
    echo "不包含"
fi

2、或者直接用 $? 检查grep的结果,结果为0表示匹配到,结果非0表示没匹配到

strA="long string"
strB="string"
echo $strA | grep "${strB}"
if [[ $? -eq 0 ]]
then
    echo "包含"
else
    echo "不包含"
fi

利用字符串运算符=~

利用字符串运算符 =~ 直接判断strA是否包含strB。

strA="helloworld"
strB="low"
if [[ $strA =~ $strB ]]
then
    echo "包含"
else
    echo "不包含"
fi

利用通配符*

用通配符 * 号代理 strA 中非 strB 的部分,如果结果相等说明包含

A="helloworld"
B="low"
if [[ $A == *$B* ]]
then
    echo "包含"
else
    echo "不包含"
fi

$()命令替换

在 bash shell 中, $() 与 `` (反引号)都是用来做命令替换(command substitution)的。

所谓命令替换即完成 或者 `$()` 里面的命令,将其结果替换出来,再重组命令行。 虽然 `$()` 和 功能一样,但更推荐使用 $(), 因为不容易引起歧义。

例如

version=$(uname -r)

version=`uname -r`

都可以使 version 得到内核的版本号


expr

expr命令可以实现数值运算、数值或字符串比较、字符串匹配、字符串提取、字符串长度计算等功能。它还具有几个特殊功能,判断变量或参数是否为整数、是否为空、是否为0等。

SHELL脚本–expr命令全解
https://www.cnblogs.com/f-ck-need-u/p/7231832.html

算数表达式

expr整数加减乘除

expr支持普通的算术操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。

+ - 加减运算。两端参数会转换为整数,如果转换失败则报错。

* / % 乘,除,取模运算。两端参数会转换为整数,如果转换失败则报错。

将shell中变量’foo’的值增加1:
foo=$(expr $foo + 1)

算术乘法符号 * 因为是shell的元字符,所以要转义,可以使用引号包围,或者使用反斜线。

$ expr $a * $b
expr: syntax error

$ expr $a '*' $b
12

$ expr $a \* $b
12

$ expr $b / $a    # 除法只能取整数
1

$ expr $b % $a
1

任意操作符两端都需要有空格,否则:

$ expr 4+$a
4+3

$ expr 4 +$a
expr: syntax error

由于expr在进行算术运算时,首先会将操作符两边的参数转换为整数,任意一端转换失败都将会报错,所以可以用来判断参数或变量是否为整数。

$ expr $a + $c
expr: non-integer argument

$ if [ $? != 0 ];then echo '$a or $c is non-integer';fi
$a or $c is non-integer
sum=`expr 2 - 5`     # sum <- -3
sum=`expr 2 + 5`     # sum <- 7
sum=`expr 3 \* 5`    # sum <- 15
sum=`expr 3 / 5`     # sum <- 0
sum=`expr 7 / 5`     # sum <- 1
sum=`expr \( 2 - 3 \) \* 6`   # sum <- -6
sum=`expr 2+4`       # sum <- 2+4
sum=`expr 2-4*6·     # sum <- 2-4*6
sum=`expr 1-(5-8)`   # sum <- 1-(5-8)

$(())运算

sum=$((3+5))           # sum <- 8
sum=$(( 3 - 5 ))       # sum <- -2
sum=$(( 3 * 5 ))       # sum <- 15
sum=$(( 7 / 5 ))       # sum <- 1
sum=$(( 7 % 5 ))       # sum <- 2
sum=$(( (1 - 2 )  * 4 ))   # sum <- -4

写法比较自由,无需对运算符和括号做转义处理,也可以采用松散或紧凑的格式.


let运算

let 命令是 BASH 中用于计算的工具,用于执行一个或多个表达式,在变量的房屋计算中不需要加上$来表示变量,如果表达式的值是非0,那么返回的状态值是0;否则,返回的状态值是1。

自加操作 let no++
自减操作 let no--
let no+=10 等同于 let no=no+10
let no-=20 等同于 let no=no-20

let "sum=3+5"    # sum <- 8
let "sum=3*5"    # sum <- 15
let "sum=2/5"    # sum <- 0
let "sum=11/5"   # sum <- 2
let "sum=11%5"   # sum <- 1
let "sum=-6-9"   # sum <- -15
let "sum=(-6-9)*5"  # sum <- -75

注意, 等号右边以及运算符和括号的两边都不能有空格


shell小数除法

export a=2
export b=17
num3=`expr $a / $b`

结果为1,因为expr不支持浮点除法,计算结果都会转为整数

解决的方法:
1、使用 bc 计算器,scale控制小数点后保留几位
echo "scale=2; $a/$b" | bc

2、使用 awk printf
awk 'BEGIN{printf "%.2f\n", '$a'/'$b'}'

逻辑表达式

‘expr’支持普通的逻辑连接和逻辑关系。它的优先级最低。

|
如果第一个参数非空且非0,则返回第一个参数的值,否则返回第二个参数的值,但要求第二个参数的值也是非空或非0,否则返回0。如果第一个参数是非空或非0时,不会计算第二个参数。

经过测试,以上手册内容是错误的。正确的应该是:如果第一个参数非0,则返回第一个参数的值,否则返回第二个参数。但如果任意一个参数为空,则报错。除非空字符串使用引号包围,此时将和0的处理方式一样。

&
如果两个参数都非空且非0,则返回第一个参数,否则返回0。如果第一个参为0或为空,则不会计算第二个参数。

经过测试,以上手册内容是错误的。正确的应该是:如果两个参数都非0,则返回第一个参数,否则返回0。但任意一个参数为空,则报错。除非空字符串使用引号包围,此时将和0的处理方式一样。

< <= = == != >= >
比较两端的参数,如果为true,则返回1,否则返回0。”==”是”=”的同义词。”expr”首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。

括号’()’可以改变优先级,但使用时需要使用反斜线对括号进行转义。

其中”<”和”>”是正则表达式正的锚定元字符,且”<”会被shell解析为重定向符号,所以需要转义或用引号包围。

$ a=3

$ expr $a = 1
0

$ expr $a = 3
1

$ expr $a \* 3 = 9
1

$ expr abc \> ab
1

$ expr akc \> ackd
1

逻辑连接符号”&”和”|”用法示例。这两个符号都需要转义,或使用引号包围。

$ expr $abc '|' 1
expr: syntax error

$ expr "$abc" '|' 1
1

$ expr "$abc" '&' 1
0

$ expr $abc '&' 1
expr: syntax error

$ expr 0 '&' abc
0

$ expr abc '&' 0
0

$ expr abc '|' 0
abc

$ expr 0 '|' abc
abc

$ expr abc '&' cde
abc

$ expr abc '|' cde
abc

expr length STRING 长度

返回STRING的字符长度。
该表达式是返回string的长度,其中string不允许为空,否则将报错,所以可以用来判断变量是否为空。

$ expr length abcde
5

$ expr length 111
3

$ expr length $xxx
expr: syntax error

$ if [ $? -ne 0 ];then echo '$xxx is null';fi
$xxx is null

expr substr 字符串截取

substr STRING POSITION LENGTH
返回STRING字符串中从POSITION开始,长度最大为LENGTH的子串。如果POSITION或LENGTH为负数,0或非数值,则返回空字符串。

$ expr substr abcde 2 3
bcd

$ expr substr abcde 2 4
bcde

$ expr substr abcde 2 5
bcde

$ expr substr abcde 2 0

$ expr substr abcde 2 -1

expr index STRING CHARSET 子串匹配

CHARSET中任意单个字符在STRING中最前面的字符位置。如果在STRING中完全不存在CHARSET中的字符,则返回0。
expr index string chars 该表达式是从string中搜索chars中某个字符的位置,这个字符是string中最靠前的字符。
例如:

$ expr index abcde dec
3
$ expr index abcde xdc
3

该命令将对字符串”dec”逐字符分解,首先分解得到第一个字符d,从abcde中搜索到d的位置为4,再分解得到第二个字符e,该字符在abcde中的位置为5,最后得到的字符是c,该字符在abcde中的位置为3。其中3是最靠前的字符,所以命令返回的结果为3。

如果chars中的所有字符都不存在于string中,则返回0。

$ expr index abcde dec
$ expr index abcde 1
0

$ expr index abcde 1x
0

expr STRING : REGEX 模式匹配

expr STRING : REGEX
expr match STRING REGEX 等价于 expr STRING : REGEX

执行模式匹配。两端参数会转换为字符格式,且第二个参数被视为正则表达式(GNU基本正则),它默认会隐含前缀”^”。随后将第一个参数和正则模式做匹配。
如果匹配成功,且REGEX使用了’(‘和’)‘,则此表达式返回匹配到的,如果未使用’(‘和’)‘,则返回匹配的字符数。
如果匹配失败,如果REGEX中使用了’(‘和’)‘,则此表达式返回空字符串,否则返回为0。
只有第一个’(…)‘会引用返回的值;其余的’(…)‘只在正则表达式分组时有意义。
在正则表达式中,’+‘,’?‘和’|‘分表代表匹配一个或多个,0个或1个以及两端任选其一的意思。

“string : REGEX”字符串匹配要输出匹配到的字符串结果,需要使用”(“和”)“,否则返回的将是匹配到的字符串数量。

expr aaa : 'a\+'
解释:因为REGEX部分没有使用(),所以返回匹配的字符数
=> 3

expr abc : 'a\(.\)c'
解释:因为REGEX部分使用了(),所以返回匹配的字符
=> b

注意,由于REGEX中隐含了”^”,所以使得匹配时都是从string首字符开始的。
expr abcde : 'cd.*' 结果为 0
之所以为0,是因为真正的正则表达式是”^cd.*”,而abcde不是c开头而是a开头的,所以无法匹配到任何结果。因此,任何字符串匹配时,都应该从首字符开始。

$ expr abcde : 'ab\(.*\)'
cde

$ expr abcde : 'ab\(.\)'
c

$ expr abcde : 'ab.*'
5

$ expr abcde : 'ab.'
3

$ expr abcde : '.*cd*'
4

Shell运算符(条件表达式)

常用判断命令:test[
[命令位于user/bin/中,是test命令的别名
命令格式:
[ expressiontest expression,expression后必须加]

# which [
/usr/bin/[
# which test
/usr/bin/test

man test命令查看如何写表达式

注意:**使用方括号[时,必须在条件两边加上空格


单方括号与双方括号

[ ] 两个符号左右都要有空格分隔
内部操作符与操作变量之间要有空格:如 [ “a” = “b” ]
字符串比较中,> < 需要写成> < 进行转义
[ ] 中字符串或者 ${} 变量尽量使用”” 双引号扩住,避免值未定义引用而出错的好办法
[ ] 中可以使用 –a –o 进行逻辑运算
[ ] 是bash 内置命令:[ is a shell builtin

[[ ]] 两个符号左右都要有空格分隔
内部操作符与操作变量之间要有空格:如 [[ “a” = “b” ]]
字符串比较中,可以直接使用 > < 无需转义
[[ ]] 中字符串或者${}变量尽量如未使用”” 双引号扩住的话,会进行模式和元字符匹配
[[ ]] 内部可以使用 && || 进行逻辑运算
[[ ]] 是bash keyword:[[ is a shell keyword

双方括号的优势:
[[ 是 bash 程序语言的关键字。并不是一个命令,[[ ]] 结构比 [ ] 结构更加通用。在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。

支持字符串的模式匹配,使用=~操作符时甚至支持shell的正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真。[[ ]] 中匹配字符串或通配符,不需要引号。

使用[[ … ]]条件判断结构,而不是[… ],能够防止脚本中的许多逻辑错误。比如,&&、||、<和> 操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。

bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码。


关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne 检测两个数是否相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。

布尔运算符

下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

逻辑运算符

以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
││ 逻辑的 OR [[ $a -lt 100 ││ $b -gt 100 ]] 返回 true

字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为0,不为0返回 true。 [ -n $a ] 返回 true。
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

注意:字符串允许使用赋值号做等号,字符串的 等于”比较,为了与POSIX一致,在[]中使用=,(尽管==也可以可以用的),注意在=前后各有一个空格,如果没有空格就是赋值的关系,不是比较的关系

设置jvm_opts环境变量默认值

如果环境变量配置了 jvm_opts 就取环境变量,否则用写死的默认值
shell-var-test.sh

#!/bin/bash
if [ -z "$jvm_opts" ]
then jvm_opts="-Xms1g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError "
else echo 'jvm_opts is set by env'
fi
echo $jvm_opts

执行 jvm_opts='xxx' sh shell-var-test.sh 结果
jvm_opts is set by env
xxx

或者可以简化为

#!/bin/bash
[ -z "$jvm_opts" ] && jvm_opts="-Xms1g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError "
echo $jvm_opts

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。
变量 file 表示文件”/var/www/runoob/test.sh”,它的大小为100字节,具有 rwx 权限

操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),
如果是,则返回 true。
[ -f $file ] 返回 true。
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。

Shell语句

if

if ... then ... else ... fi

#!/bin/bash

read VAR

# 下面这两种判断方法都可以,使用 [] 注意左右加空格
#if test $VAR -eq 10
if [ $VART -eq 10 ]
then
    echo "true"
else
    echo "false"
fi

case

#!/bin/bash

read NAME
# 格式有点复杂,一定要注意
case $NAME in
    "Linux")
        echo "Linux"
        ;;
    "cdeveloper")
        echo "cdeveloper"
        ;;
    *)
        echo "other"
        ;;
esac

for循环

#!/bin/bash

# 普通 for 循环
for ((i = 1; i <= 3; i++))
do
    echo $i
done


# VAR 依次代表每个元素
for VAR in 1 2 3
do
    echo $VAR
done

while循环

while循环示例1

#!/bin/bash
int=1
while(( $int<=5 ))
do
    echo $int
    let "int++"
done

while循环示例2

#!/bin/bash
VAR=1
# 如果 VAR 小于 10,就打印出来
while [ $VAR -lt 10 ]
do
    echo $VAR
#   VAR 自增 1
    VAR=$[ $VAR + 1 ]
done

until循环

until 循环的结束条件为 1
until 循环执行一系列命令直至条件为 true 时停止。

until 循环格式为

until command
do
   Statement(s) to be executed until command is true
done

command 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。

#!/bin/bash

i=0

# i 大于 5 时,循环结束
until [[ "$i" -gt 5 ]]
do
    echo $i
    i=$[ $i + 1 ]
done

工具

ShellCheck shell语法检查工具

ShellCheck - A shell script static analysis tool
https://www.shellcheck.net/
https://github.com/koalaman/shellcheck

Shell 教程 | 菜鸟教程
http://www.runoob.com/linux/linux-shell.html

shell编程——if语句 if -z -n -f -eq -ne -lt
http://www.dutycode.com/post-46.html


实例

  • !!,连续两个英文叹号,表示执行上一条指令

  • $n,n为数字,$n为执行脚本的第一个参数,相当于main函数的argv[n]。例如,$0就是argv[0],即脚本程序自身的名称

  • $#,传递到脚本的参数个数,不包括脚本本身

  • $*,以一个单字符串显示所有向脚本传递的参数,不包括$0

  • $@,与$*类似,从$1开始的所有参数,但每个都作为独立的字符串

$*$@的区别
相同点:都是引用所有参数。
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则"$*"等价于 “1 2 3”(单个字符串),而"$@"等价于 “1” “2” “3”(三个字符串)。

  • $?:当前shell进程中,上一个命令的返回值,如果上一个命令成功执行则$?的值为0,否则为其他非零值,常用做if语句条件
  • $$:当前shell进程的pid
  • $!:后台运行的最后一个进程的pid
  • $-:显示Shell使用的当前选项,与set命令功能相同。
  • $_:之前命令的最后一个参数

打印nginx的进程号

ps -ef|grep nginx|grep master|awk '{print $2}'

统计单词出现的次数

1、统计文本中每个单词(空格、制表、换行分隔)出现的次数?

masikkk masikkk xx xx x x     kkk kk  kkk
english    count  ? com         masikkk
madaimeng    devgou  com

解答:
cat word_count.txt| tr -s ' \t' '\n' | sort |uniq -c
结果:

1 ?
2 com
1 count
1 devgou
1 english
1 kk
2 kkk
1 madaimeng
3 masikkk
2 x
2 xx

解释:tr -s ' \t' '\n' 连续的 空格 和 制表符 压缩为1个,并替换为换行符

2、找出出现次数最多的3个单词?
cat word_count.txt| tr -s ' \t' '\n' | sort |uniq -c|sort -r|head -3
结果

3 masikkk
2 xx
2 x

统计nginx日志中出现次数最多的10个ip

1、Nginx access 日志如下,里面有ip字段,写shell脚本找出出现次数最多的10个ip,按出现次数从高到低倒序排列输出?
access-10000.log

3.112.230.252 - - [11/Mar/2020:13:52:40 +0800] "GET /eureka/apps/delta HTTP/1.1" 200 89 "-" "Java-EurekaClient/v1.9.13" "-"
3.112.230.252 - - [11/Mar/2020:13:52:40 +0800] "GET /eureka/apps/delta HTTP/1.1" 200 89 "-" "Java-EurekaClient/v1.9.13" "-"
3.112.230.252 - - [11/Mar/2020:13:52:48 +0800] "PUT /eureka/apps/STATISTIC/ip-172-26-4-123.ap-northeast-1.compute.internal:statistic:8002?status=UP&lastDirtyTimestamp=1583842800449 HTTP/1.1" 200 0 "-" "Java-EurekaClient/v1.9.13" "-"
3.112.230.252 - - [11/Mar/2020:13:52:50 +0800] "PUT /eureka/apps/DISQUS/ip-172-26-4-123.ap-northeast-1.compute.internal:disqus:8001?status=UP&lastDirtyTimestamp=1583842811792 HTTP/1.1" 200 0 "-" "Java-EurekaClient/v1.9.13" "-"
220.181.108.108 - - [11/Mar/2020:13:54:01 +0800] "GET /blog/IMG_3600.JPG?imageMogr2/auto-orient HTTP/1.1" 200 1370025 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" "-"
66.249.79.155 - - [11/Mar/2020:13:54:39 +0800] "GET /article/Java-Interview-3-Concurrent/ HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "-"
123.57.52.15 - - [11/Mar/2020:13:56:02 +0800] "GET /article/TortoiseSVN/ HTTP/1.1" 200 39095 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" "-"
220.181.108.160 - - [11/Mar/2020:13:56:36 +0800] "GET /tags/CV/ HTTP/1.1" 200 614440 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" "-"
3.112.230.252 - - [11/Mar/2020:13:56:40 +0800] "GET /eureka/apps/delta HTTP/1.1" 200 89 "-" "Java-EurekaClient/v1.9.13" "-"
3.112.230.252 - - [11/Mar/2020:13:56:40 +0800] "GET /eureka/apps/delta HTTP/1.1" 200 89 "-" "Java-EurekaClient/v1.9.13" "-"
124.166.232.105 - - [11/Mar/2020:13:56:42 +0800] "GET /tags/CV/ HTTP/1.1" 200 614440 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" "-"
207.46.13.88 - - [11/Mar/2020:13:56:49 +0800] "GET /comments?pathname=%2Fcategories%2F HTTP/1.1" 200 294 "-" "msnbot/2.0b (+http://search.msn.com/msnbot.htm)" "-"
111.206.221.51 - - [11/Mar/2020:13:56:52 +0800] "POST /statistic HTTP/1.1" 200 400 "http://masikkk.com/tags/CV/" "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1 (compatible; Baiduspider-render/2.0; +http://www.baidu.com/search/spider.html)" "-"
220.194.45.154 - - [11/Mar/2020:13:57:03 +0800] "GET /tags/Raft/ HTTP/1.1" 304 0 "http://masikkk.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
220.194.45.154 - - [11/Mar/2020:13:57:04 +0800] "POST /statistic HTTP/1.1" 200 401 "http://masikkk.com/tags/Raft/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
220.194.45.154 - - [11/Mar/2020:13:57:05 +0800] "GET /favicon.ico HTTP/1.1" 200 4286 "http://masikkk.com/tags/Raft/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
3.112.230.252 - - [11/Mar/2020:13:57:10 +0800] "GET /eureka/apps/delta HTTP/1.1" 200 89 "-" "Java-EurekaClient/v1.9.13" "-"
220.194.45.154 - - [11/Mar/2020:13:57:10 +0800] "GET /article/Raft/ HTTP/1.1" 200 40857 "http://masikkk.com/tags/Raft/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
3.112.230.252 - - [11/Mar/2020:13:57:10 +0800] "GET /eureka/apps/delta HTTP/1.1" 200 89 "-" "Java-EurekaClient/v1.9.13" "-"
220.194.45.154 - - [11/Mar/2020:13:57:10 +0800] "GET /comments?pathname=%2Farticle%2FRaft%2F HTTP/1.1" 200 37 "http://masikkk.com/article/Raft/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
220.194.45.154 - - [11/Mar/2020:13:57:11 +0800] "POST /statistic HTTP/1.1" 200 401 "http://masikkk.com/article/Raft/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
220.194.45.154 - - [11/Mar/2020:13:57:13 +0800] "GET /update/ HTTP/1.1" 200 220557 "http://masikkk.com/article/Raft/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
220.194.45.154 - - [11/Mar/2020:13:57:14 +0800] "GET /comments?pathname=%2Fupdate%2F HTTP/1.1" 200 294 "http://masikkk.com/update/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
220.194.45.154 - - [11/Mar/2020:13:57:14 +0800] "GET /statistic/ranks?limit=15 HTTP/1.1" 200 1280 "http://masikkk.com/update/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
220.194.45.154 - - [11/Mar/2020:13:57:14 +0800] "POST /statistic HTTP/1.1" 200 442 "http://masikkk.com/update/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
3.112.230.252 - - [11/Mar/2020:13:57:19 +0800] "PUT /eureka/apps/STATISTIC/ip-172-26-4-123.ap-northeast-1.compute.internal:statistic:8002?status=UP&lastDirtyTimestamp=1583842800449 HTTP/1.1" 200 0 "-" "Java-EurekaClient/v1.9.13" "-"
3.112.230.252 - - [11/Mar/2020:13:57:20 +0800] "PUT /eureka/apps/DISQUS/ip-172-26-4-123.ap-northeast-1.compute.internal:disqus:8001?status=UP&lastDirtyTimestamp=1583842811792 HTTP/1.1" 200 0 "-" "Java-EurekaClient/v1.9.13" "-"

第一列就是 ip 先用 cut 把第一列单独提出来 cut -d' ' -f1 access-10000.log
或者用 awk 提取 awk '{print $1}' access-10000.log
然后排序,统计个数,再排序取top n即可
cut -d' ' -f1 access-10000.log | sort |uniq -c| sort -r |head -10
或者
awk '{print $1}' access-10000.log | sort |uniq -c|sort -r|head -10
结果为:

10 3.112.230.252
10 220.194.45.154
 1 66.249.79.155
 1 220.181.108.160
 1 220.181.108.108
 1 207.46.13.88
 1 124.166.232.105
 1 123.57.52.15
 1 111.206.221.51

2、找出 2020.3.11 日 出现次数超过 5 的所有 ip,统计个数后倒序输出
grep 11/Mar/2020 access-10000.log 根据日期筛选出行,然后
awk '{print $1}' access-10000.log | sort |uniq -c|awk '{if ($1 >= 50) print}'| sort -r
解释:提取第一列 ip, 因为uniq统计的重复行必须是相邻的,所以必须先把所有ip排序,然后 uniq统计重复行,awk 根据第一列个数过滤出大于50的,再倒序排序后输出

3、找出所有 get 请求中访问量最高的10个页面(或api,或 uri 或 pathname),例如 GET /article/hexo-17-FontAwesome/ 不包含 *.js,*.css,*.png, favicon.ico 这些静态文件
grep GET access-10000.log |egrep -v 'favicon.ico|.css|.js|.png' |awk '{print $7}' | sort | uniq -c | sort -r | head -10
解释:先过滤出有 GET 的行,再过滤 不包含 favicon.ico|.css|.js|.png 的行,awk打印第7列,也就是 pathname 列,找出top 10
结果如下

6 /eureka/apps/delta
2 /tags/CV/
1 /update/
1 /tags/Raft/
1 /statistic/ranks?limit=15
1 /comments?pathname=%2Fupdate%2F
1 /comments?pathname=%2Fcategories%2F
1 /comments?pathname=%2Farticle%2FRaft%2F
1 /blog/IMG_3600.JPG?imageMogr2/auto-orient
1 /article/TortoiseSVN/

shell调接口并处理返回json

读取当前目录下的图片,转为 base64 编码,调接口1,拿到返回json中的 user_id 值,再去调接口2

#!/bin/bash
for file in $(ls *.{png,jpg,jpeg})
do
  echo 处理 $file
  base64str=$(base64 -i $file)
  #echo $base64str
  res1=$(curl -X POST "http://localhost:8380/interface1/image" -H "Content-Type: application/json" -d "{\"imageBase64\": \"$base64str\"}")
  echo 接口1返回: $res1
  user_id=$(echo $res1 | sed 's/,/\n/g' | grep user_id | sed 's/:/\n/g' | sed '1d' | sed 's/\"//g')
  echo user_id: $user_id
  res2=$(curl "http://localhost:8380/interface2?userId=${user_id}")
  echo 接口2返回: $res2
done

提取 user_id 的过程:
sed 's/,/\n/g' 将逗号替换为换行,变成多行
grep person_id 找到有 user_id 的一行
sed 's/:/\n/g' 将冒号替换为换行,变成 user_id 和其value各一行
sed '1d' 删除第一行,即删除 “user_id”
sed 's/\"//g' 将双引号替换为空白,即删除双引号

Shell使用grep和sed命令提取json数据中指定字段的值
https://blog.csdn.net/kakabuqinuo/article/details/99845797


springboot关闭jar脚本

#!/bin/sh

echo "Start to exec $0 ..."

# Kill Spring fat jar with given pid
killJar(){
  if [ $# -ge 2 ]; then
    echo "Kill Process $1, PID $2"
    kill -9 $2
  else
    echo "Warn kill, PID is null"
  fi
}

# Kill all 'myapp*.jar'
for pid in `ps -ef|grep 'java'|grep myapp |grep -v grep|awk '{print $2}'`
do
  echo "kill pid: $pid"
  killJar $pid $pid
done

上一篇 CentOS-6.8安装笔记

下一篇 首次组装电脑(3)系统安装

阅读
评论
13.7k
阅读预计58分钟
创建日期 2016-08-01
修改日期 2021-04-28
类别
目录
  1. Shell脚本概述
    1. 执行Shell脚本的两种方法
      1. 作为可执行程序
      2. 作为解释器参数
    2. 多个命令一起执行的方法
      1. ; 多个命令顺序执行结果不影响
      2. && 前命令成功才执行后续命令
      3. || 前命令成功则不执行后续命令
    3. Shell提示符$和#的区别
    4. shell初始化过程
    5. 换行与注释
      1. 多行脚本与注释
  2. 重定向
    1. 重定向命令列表
    2. 输出重定向
    3. 输入重定向
    4. << EOF 多行输入重定向
    5. 同时输入输出重定向
    6. stdin(0),stdout(1),stderr(2)
    7. 执行命令或shell脚本并将输出重定向到文件
  3. Shell变量
    1. shell脚本与环境变量
    2. env,set,export区别
    3. shell变量类别
      1. shell局部变量
      2. 用户环境变量
    4. 定义变量
    5. 命令行中设置变量
    6. unset删除变量
    7. 使用变量
    8. 特殊变量
      1. $*与$@的区别
  4. Shell字符串
    1. 单引号和双引号
    2. 字符串拼接
    3. ${}变量替换
      1. 删除左边字符
      2. ${var%}删除右边字符
      3. ${var:start:len} 截取字符串
      4. ${变量/查找值/替换值}字符串替换
      5. 字符串分割
    4. 获取字符串长度
    5. 字符串是否包含子串
      1. 利用grep查找
      2. 利用字符串运算符=~
      3. 利用通配符*
    6. $()命令替换
  5. expr
    1. 算数表达式
      1. expr整数加减乘除
      2. $(())运算
      3. let运算
      4. shell小数除法
    2. 逻辑表达式
    3. expr length STRING 长度
    4. expr substr 字符串截取
    5. expr index STRING CHARSET 子串匹配
    6. expr STRING : REGEX 模式匹配
  6. Shell运算符(条件表达式)
    1. 单方括号与双方括号
    2. 关系运算符
    3. 布尔运算符
    4. 逻辑运算符
    5. 字符串运算符
      1. 设置jvm_opts环境变量默认值
    6. 文件测试运算符
  7. Shell语句
    1. if
    2. case
    3. for循环
    4. while循环
    5. until循环
  8. 工具
    1. ShellCheck shell语法检查工具
  9. 实例
    1. 打印nginx的进程号
    2. 统计单词出现的次数
    3. 统计nginx日志中出现次数最多的10个ip
    4. shell调接口并处理返回json
    5. springboot关闭jar脚本

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论