Shell脚本入门学习

shell脚本语言的使用

shell概述

shell 是一种脚本语言。

脚本:本质是一个文件,文件里面存放的是 特定格式的指令,系统可以使用脚本解析器 翻译或解析 指令 并执行(它不需要编译)。

举个例子,我想实现这样的操作:

1)进入到/tmp/目录;

2)列出当前目录中所有的文件名;

3)把所有当前的文件拷贝到/root/目录下;

4)删除当前目录下所有的文件。

简单的4步在shell窗口中需要你敲4次命令,按4次回车。这样是不是很麻烦?当然这4步操作非常简单,如果是更加复杂的命令设置需要几十次操作呢?那样的话一次一次敲键盘会很麻烦。

所以不妨把所有的操作都记录到一个文档中,然后去调用文档中的命令,这样一步操作就可以完成。其实这个文档呢就是shell脚本了,只是这个shell脚本有它特殊的格式。

shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。(应用程序 解析 脚本语言)。

Shell脚本能帮助我们很方便的去管理服务器,因为我们可以指定一个任务计划定时去执行某一个shell脚本实现我们想要需求。

这对于Linux系统管理员来说是一件非常值得自豪的事情。现在的139邮箱很好用,发邮件的同时还可以发一条邮件通知的短信给用户,利用这点,我们就可以在我们的Linux服务器上部署监控的shell脚本,比如网卡流量有异常了或者服务器web服务器停止了就可以发一封邮件给管理员,同时发送给管理员一个报警短信这样可以让我们及时的知道服务器出问题了。

c脚本编写

Linux 的内核、shell、基础命令程序,都是C语言编写的

yum install gcc -y  # 安装gcc编译

c语言脚本hello.c

# include
void main(){
 printf("hello world!\n");
}

执行c语言脚本

gcc hello.c  # 默认编译生成a.out文件
ls
 a.out  hello  hello.c  master.tar.gz  test.sh
./a.out  # 执行编译之后的a.out文件
 hello world!
gcc hello.c -o hello   # -o表示编译成为自己想要的文件名字
ls
 a.out  hello  hello.c  master.tar.gz  test.sh
./hello  # 运行c脚本
 hello world!

Shell 环境

Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。

Linux 的 Shell 种类众多,常见的有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash)
    • 在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)
  • ……

#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。

第一个shell脚本

打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 test.sh,扩展名为 sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了。

shell脚本通常都是以.sh 为后缀名的,这个并不是说不带.sh这个脚本就不能执行,只是大家的一个习惯而已。

有一个问题需要约定一下,凡是自定义的脚本建议放到/usr/local/sbin/目录下,这样做的目的是:

一来可以更好的管理文档。

二来以后接管你的管理员都知道自定义脚本放在哪里,方便维护。

【shell脚本的基本结构以及如何执行】

Shell脚本入门学习_第1张图片

查看自己Linux系统的默认解析:echo $SHELL

echo $SHELL
/bin/bash

实例

#!/bin/bash
echo "Hello World !"

1、定义以开头:#!/bin/bash

单个"#"号代表注释当前行第一步:编写脚本文件

2、加上可执行权限

chmod +x xxxx.sh

默认我们用vim编辑的文档是不带有执行权限的,所以需要加一个执行权限,那样就可以直接使用’./filename’ 执行这个脚本了。

3、运行

三种执行方式 (./xxx.sh |bash xxx.sh| . xxx.sh

./xxx.sh :先按照 文件中#!指定的解析器解析

  • 如果#!指定指定的解析器不存在,才会使用系统默认的解析器

bash xxx.sh:指明先用bash解析器解析

使用sh命令去执行一个shell脚本的时候是可以加-x选项来查看这个脚本执行过程的,这样有利于我们调试这个脚本哪里出了问题。

sh -x return_test.sh
 + sayhello junma
 + '[' junma ']'
 + echo hello junma
 hello junma
 + echo 0
 0
 + sayhello jinlong
 + '[' jinlong ']'
 + echo hello jinlong
 hello jinlong
 + echo 0
 0
 + sayhello
 + '[' '' ']'
 + echo 'give me a name please!'
 give me a name please!
 + return 1
 + echo 1
 1
  • 如果bash不存在,才会使用默认解析器

. xxx.sh 直接使用默认解析器解析

三种执行情况:(重要)

Shell脚本入门学习_第2张图片

#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。

\#!用来声明脚本由什么shell解释,否则使用默认shell

echo 命令用于向窗口输出文本。

shell脚本是一种脚本语言,我们只需使用任意文本编辑器,按照语法编写相应程序,增加可执行权限,即可在安装shell命令解释器的环境下执行。
你有没有用过这样的命令/etc/init.d/iptables restart,其实前面的/etc/init.d/iptables文件其实就是一个shell脚本,为什么后面可以跟一个”restart”? 这里就涉及到了shell脚本的预设变量。实际上,shell脚本在执行的时候后边是可以跟变量的,而且还可以跟多个。不妨、写一个脚本,看看就会明白了。

# test.sh
#!/bin/bash
sum=$[$1+$2]
echo $sum

执行过程如下:

sh -x test.sh 19 20
sum=39
echo 39
 39

在脚本中,你会不会奇怪,哪里来的$1$2,这其实就是shell脚本的预设变量,其中$1的值就是在执行的时候输入的$1,而$2的值就是执行的时候输入的$2,当然一个shell脚本的预设变量是没有限制的,这回你明白了吧。另外还有一个$0,不过它代表的是脚本本身的名字。不妨把脚本修改一下。

# test.sh
#!/bin/bash
sum=$[$1+$2]
echo "$0 $1 $2"

执行过程如下:

sh test.sh 20 20
 test.sh 20 20

退出状态码

每个命令执行后都会有对应的进程退出状态码,用来表示该进程是否是正常退出。
所以,在命令行中,在shell脚本中,经常会使用特殊变量$?判断最近一个前台命令是否正常退出。
通常情况下,如果$?的值:

  • 为0,表示进程成功执行,即正常退出

  • 非0,表示进程未成功执行,即非正常退出

  • 但非0退出状态码并不一定表示错误,也可能是正常逻辑的退出。

另外,在shell脚本中,所有条件判断(比如if语句、while语句)都以0退出状态码表示True,以非0退出状态码为False。

exit命令

exit命令可用于退出当前shell进程,比如退出当前shell终端、退出shell脚本,等等。

exit [N]

exit可指定退出状态码N,如果省略N,则默认退出状态码为0,即表示正确退出。

后台执行命令&

在命令的结尾使用&符号,可以将这个命令放入后台执行。
命令放入后台后,会立即回到shell进程,shell进程会立即执行下一条命令(如果有)或退出。
使用$!可以获取最近一个后台进程的PID。

sleep  20 &  
 [1] 14968
echo  $!
14968

使用wait命令可以等待后台进程(当前shell进程的子进程)完成:

wait [n1 n2 n3 ...]

不给定任何参数时,会等待所有子进程(即所有后台进程)完成。

sleep 5 &
 [2] 15316
 [1]   Done                    sleep 20
wait
echo haha
 haha

多命令组合

shell中有多种组合多个命令的方式。

  1. cmd1退出后,执行cmd2
cmd1;cmd2
  1. cmd1正确退出(退出状态码为0)后,执行cmd2
cmd1 && cmd2
  1. cmd1不正确退出后,执行cmd2
cmd1 || cmd2
  1. 逻辑结合:&&||可以随意结合
# cmd1正确退出后执行cmd2,cmd2正确退出后执行cmd3
cmd1 && cmd2 && cmd3...

# cmd1正确退出则执行cmd2,cmd1不正确退出会执行cmd3
# cmd1正确退出,但cmd2不正确退出,也会执行cmd3
cmd1 && cmd2 || cmd3

# cmd1正确退出会执行cmd3
# cmd1不正确退出会执行cmd2,cmd2正确退出会执行cmd3
cmd1 || cmd2 && cmd3
  1. 将多个命令分组:小括号或大括号可以组合多个命令
# 小括号组合的多个命令是在子shell中执行
# 即会先创建一个新的shell进程,再执行里面的命令
(cmd1;cmd2;cmd3)

# 大括号组合的多个命令是在当前shell中执行
# 大括号语法特殊,要求:
#   1.开闭括号旁边都有空白,否则语法解析错误(解析成大括号扩展)
#   2.写在同一行时,每个cmd后都要加分号结尾
#   3.多个命令可分行书写,不要求分号结尾
{ cmd1;cmd2;cmd3; }
{
  cmd1
  cmd2
  cmd3
}

变量

顾名思义,变量就是其值可以变化的量。变量名是指向一片用于存储数据的内存空间。变量有局部变量,环境变量之分。

bash中基本数据类型只有字符串类型,连数值类型都没有(declare -i可强制声明数值类型)。

  • 局部变量就是指在某个Shell中生效的变量,对其他Shell来说无效。
    • 每个shell文件运行时都处于一个单独的进程中,独自分配内存空间。
  • 环境变量又称为全局变量。
    • 自定义环境变量
    • 系统预设的环境变量

shell变量是一种弱类型的变量,不像在C语言中,变量必须要先声明再使用。

shell中变量名区分大小写。

定义变量

变量名=变量值

注意:=两边不能有空格!

如:

name=Kitty

变量替换

变量替换是指在命令开始执行前,shell会先将变量的值替换到引用变量的位置处。
例如:

a="hello" 
echo $a world

在echo命令开始执行前,shell会取得变量a的值hello,并将它替换到命令行的$a处。于是,在echo命令开始执行时,命令行已经变成:

echo hello world

除了变量替换,Shell还会做其它替换:

  • 命令替换
  • 进程替换
  • 算术运算替换
  • 大括号扩展
  • 波浪号扩展
  • 路径扩展
    这些扩展和替换,都是shell在调用命令之前就完成的,这和其它语言解析代码的方式不一样。

学习Shell如何做命令行解析很重要,如果不掌握命令行解析,当遇到命令行语法错误后很可能会花掉大量无谓的时间去调试命令。而掌握命令行解析后,就会对命令生命周期了如执掌,不敢说一次就能写对所有命令行,但能节省大量调试时间,对写命令行和写脚本的能力也会上升一个层次。

引用变量

$变量名

如:

$name

清除变量值

unset 变量名

如:

unset name

只读变量

readonly num=10 # readonly只读
echo "num=$num"
 num=10
num=200
 -bash: num: readonly variable

查看环境变量

env
NVM_INC=/www/server/nvm/versions/node/v16.13.2/include/node
XDG_SESSION_ID=463
HOSTNAME=lw
NVM_CD_FLAGS=
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=183.230.190.196 63586 22
SSH_TTY=/dev/pts/0
NVM_DIR=/www/server/nvm
USER=root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/root
PATH=/www/server/nvm/versions/node/v16.13.2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
PWD=/root
LANG=en_US.UTF-8
HISTCONTROL=ignoredups
SHLVL=1
HOME=/root
LOGNAME=root
SSH_CONNECTION=183.230.190.196 63586 172.23.217.49 22
NVM_BIN=/www/server/nvm/versions/node/v16.13.2/bin
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/0
LE_WORKING_DIR=/root/.acme.sh
_=/usr/bin/env

导出环境变量(其他shell识别该变量)

source 脚本文件

source命令用法:

source FileName

作用:在当前bash环境下读取并执行FileName中的命令。

注:该命令通常用命令“.”来替代。

如:source .bash_rc. .bash_rc是等效的。

source在当前bash环境下执行命令,而scripts是启动一个子shell来执行命令。这样如果把设置环境变量(或alias等等)的命令写进scripts中,就只会影响子shell,无法改变当前的BASH,所以通过文件(命令列)设置环境变量时,要用source命令。

可以在终端中读取:

echo "$USER"
 root

在其他sh脚本读取:

#!/bin/bash
echo "$USER"
 root

注意事项:

1、变量名只能包含英文字母下划线,不能以数字开头

1_num=10 # 错误
num_1=20 # 正确

2、等号两边不能直接接空格符,若变量中本身就包含了空格,则整个字符串都要用双引号、或单引号括起来
3、双引号 单引号的区别

  • 双引号:可以解析变量的值
  • 单引号:不能解析变量的值

如果想在PATH变量中 追加一个路径写法如下:(重要!!!!)

export PATH=$PATH:/需要添加的路径

预设变量

Shell脚本入门学习_第3张图片

变量的扩展

判断变量是否存在

{num:-val} 如果num存在,整个表达式的值为num,否则为val

echo ${name:-是是是}
 是是是

{num:=val} 如果num存在,整个表达式的值为num,否则为val,同时将num的值赋值为val

echo ${name:=是是是} # 100
 是是是
echo $name
 是是是
字符串的操作
字符串串联

字符串的串联操作,直接将两段数据连接在一起即可,不需要任何操作符。

echo "junma""jinlong"
 junma jinlong
echo 1234 5678
 1234 5678
测量字符串的长度

${#str}

str="一从大地起风雷,便有精生白骨堆。僧是愚氓犹可训,妖为鬼蜮必成灾。"
echo ${#str}
 32
从某下标提取字符

${str:i}

str="一从大地起风雷,便有精生白骨堆。僧是愚氓犹可训,妖为鬼蜮必成灾。"
echo ${str:16}
 僧是愚氓犹可训,妖为鬼蜮必成灾。
从某下标提取定长字符

${str:i:length}

str="一从大地起风雷,便有精生白骨堆。僧是愚氓犹可训,妖为鬼蜮必成灾。"
echo ${str:0:16} 
 一从大地起风雷,便有精生白骨堆。
替换字符串中第一次出现的某字符

${str/old/new}

str="一从大地起风雷,便有精生白骨堆。僧是愚氓犹可训,妖为鬼蜮必成灾。"
echo ${str//|}
 一从大地起风雷|便有精生白骨堆。僧是愚氓犹可训,妖为鬼蜮必成灾。
替换字符串中所有出现的某字符

${str//old/new}

str="一从大地起风雷,便有精生白骨堆。僧是愚氓犹可训,妖为鬼蜮必成灾。"
echo ${str///---}
 一从大地起风雷---便有精生白骨堆。僧是愚氓犹可训---妖为鬼蜮必成灾。

基本运算符

字符串运算符

Shell脚本入门学习_第4张图片

布尔运算符

Shell脚本入门学习_第5张图片

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

Shell脚本入门学习_第6张图片

其他检查符:

  • -S: 判断某文件是否 socket。
  • -L: 检测文件是否存在并且是一个符号链接。
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

file="/var/www/runoob/test.sh"
if [ -r $file ]
then
   echo "文件可读"
else
   echo "文件不可读"
fi
if [ -w $file ]
then
   echo "文件可写"
else
   echo "文件不可写"
fi
if [ -x $file ]
then
   echo "文件可执行"
else
   echo "文件不可执行"
fi
if [ -f $file ]
then
   echo "文件为普通文件"
else
   echo "文件为特殊文件"
fi
if [ -d $file ]
then
   echo "文件是个目录"
else
   echo "文件不是个目录"
fi
if [ -s $file ]
then
   echo "文件不为空"
else
   echo "文件为空"
fi
if [ -e $file ]
then
   echo "文件存在"
else
   echo "文件不存在"
fi

条件测试

test命令:用于测试字符串、文件状态和数字

test命令有两种格式:

test命令或功能等价的Bash内置命令[ ]可以做条件测试;此外,还可以使用[[]]来做条件测试,甚至let、$[]、$(())也可以做条件测试。

  • test condition
  • [ condition ]
    • 使用方括号时,要注意在条件两边加上空格。
[  ]
echo $?
 1

test
echo $?
 1

没有任何测试内容时,直接返回false。

文件测试

Shell脚本入门学习_第7张图片

#!/bin/bash
read -p "请输入一个文件名:" fileName
 请输入一个文件名:test.sh
test -e $fileName
# [ -e $fileName ] # 和上面read -p "请输入一个文件名:" fileName等效
echo $?
 0 # 0表示该文件存在,非0表示不存在
文件类测试
条件表达式 含义
-e file 文件是否存在(exist)
-f file 文件是否存在且为普通文件(file)
-d file 文件是否存在且为目录(directory)
-b file 文件是否存在且为块设备block device
-c file 文件是否存在且为字符设备character device
-S file 文件是否存在且为套接字文件Socket
-p file 文件是否存在且为命名管道文件FIFO(pipe)
-L file 文件是否存在且是一个链接文件(Link)
文件属性类测试
条件表达式 含义
-r file 文件是否存在且当前用户可读
-w file 文件是否存在且当前用户可写
-x file 文件是否存在且当前用户可执行
-s file 文件是否存在且大小大于0字节,即检测文件是否非空文件
-N file 文件是否存在,且自上次read后是否被modify
两文件之间的比较
条件表达式 含义
file1 -nt file2 (newer than)判断file1是否比file2新
file1 -ot file2 (older than)判断file1是否比file2旧
file1 -ef file2 (equal file)判断file1与file2是否为同一文件

字符串测试

#!/bin/bash
test -z $yn
echo $? #0
 0

read -p "please input y/n:" yn
 please input y/n:[ -z $yn ]
echo "1:$?"
 1:1
 
[ $yn = "y" ]
echo "2:$?"
 2:0

Shell脚本入门学习_第8张图片

#!/bin/bash
test -z $yn
echo $? #0
 0
#!/bin/bash
read -p "请输入第一个字符串:" str1
 请输入第一个字符串:abc
read -p "请输入第二个字符串:" str2
 请输入第二个字符串:def
test $str1 = $str2
echo $?
 1
read -p "请输入第一个字符串:" str1
 请输入第一个字符串:123
read -p "请输入第二个字符串:" str2
 请输入第二个字符串:123
[ $str1 = $str2 ]
echo $?
 0

数值测试

Shell脚本入门学习_第9张图片

#!/bin/bash
read -p "请输入第一个数值:" data1
 请输入第一个数值:19990221
read -p "请输入第二个数值:" data2
 请输入第二个数值:2135139861
test $data1 -eq $data2
echo "相等:$?"
 相等:1
 
[ $data1 -ge $data2 ]
echo "大于等于:$?"
 大于等于:1

[ $data1 -lt $data2 ]
echo "小于:$?"
 小于:0

符合语句测试

Shell脚本入门学习_第10张图片

Shell脚本入门学习_第11张图片

test -e /home && test -d /home && echo "true"
 true
test "aaa" = "aaa" || echo "not equal" && echo "equal"
 equal
test 2 -lt 3 && test 5 -gt 3 && echo "equal"
 equal
# test.sh文件同时具有r和x权限时,才为true
test -r test.sh -a -x test.sh
echo $?
 0
# test.sh文件同时具有r或x权限时,就传回true
test -r test.sh -o -x test.sh
echo $?
 0
# 当test.sh文件不具有x时,回传true
test ! -x test.sh
echo $?
 1

控制语句

if控制语句

格式一:
if [条件1]; then
  执行第一段程序
else
  执行第二段程序
fi
#################
格式二:
if [条件1]; then
  执行第一段程序
elif [条件2];then
执行第二段程序
else
  执行第三段程序
fi
案例
# test.sh(vim编辑test.sh脚本——`vi test.sh`)
#!/bin/bash
read -p "请输入y继续:" yn
if [ $yn = "y" ]; then
	echo "继续执行"
else
   	echo "停止执行"
fi
sh test.sh
 请输入y继续:y
 继续执行
sh test.sh
 请输入y继续:n
 停止执行

案例:字符串比较是否为 null

# test.sh
#!/bin/bash
a=""
if [ -n $a ]
then
   echo "-n $a : 字符串长度不为 0"
else
   echo "-n $a : 字符串长度为 0"
fi
sh test.sh
 -n  : 字符串长度为0

注意:是 $a 这里应该加上双引号,否则 -n $a 的结果永远是 true。

案例:判断当前路径下有没有文件夹,有就进入创建文件,没有 就创建文件夹,再进入创建文件。

# test.sh
#!/bin/bash
read -p "请输入文件夹的名字:" dirName
# 判断文件夹是否存在
if [ -e $dirName ]; then
	echo "$dirName 是存在的 即将进入文件夹"
	cd $dirName
	echo "即将创建文件名为test.c"
	touch test.c
else # 不存在
	echo "该文件夹 是 不存在的 即将创建该文件夹"
	mkdir $dirName
	echo "进入$dirName里面"
	cd $dirName
	echo "即将创建文件test.c"
	touch test.c
fi
sh test.sh
 请输入文件夹的名字:test01
 test01 是存在的 即将进入文件夹
 即将创建文件名为test.c
ls /
bin  boot  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  patch  proc  root  run  sbin  srv  sys  tmp  usr  var  www
pwd
 /root
ls /root/
 master.tar.gz  myfile  test01  test.sh
ls /root/test01/
 test.c

Linux 删除文件夹和文件的命令

-r 就是向下递归,不管有多少级目录,一并删除
-f 就是直接强行删除,不作任何提示的意思

删除文件夹实例:
rm -rf /root/test01

将会删除/root/test01目录以及其下所有文件、文件夹

删除文件使用实例:
rm -f /var/log/httpd/access.log

将会强制删除/var/log/httpd/access.log这个文件

案例:

# test.sh
#!/bin/bash
read -p "请输入一个文件夹的名字:" dirName
if [ -e $dirName ]; then
	cd $dirName
else
mkdir $dirName
	cd $dirName
fi
read -p "请输入y创建文件,n直接退出" yes
if [ $yes = "y" ]; then
read -p "请输入文件名:" fileName
	touch $fileName
elif [ $yes = "n" ]; then
	echo "退出了!"
fi
sh test.sh
 输入一个文件夹的名字:test01   
 请输入y创建文件,n直接退出y
 请输入文件名:test1
ls /root/test01
 test1

case选择语句

case脚本常用于编写系统服务的启动脚本,例如/etc/init.d/iptables中就用到了,你不妨去查看一下。

Shell脚本入门学习_第12张图片

# test.sh
#!/bin/bash
read -p "请输入yes/no:" choice
case $choice in
	yes | y* | Y* )
	echo "输入了yes"
	;;
	no | n* | N* )
	echo "输入了no"
	;; # break
	* ) # default
	echo "输入了其他"
	;;
esac
sh test.sh
 请输入yes/no:yes
 输入了yes
sh test.sh
 请输入yes/no:no
 输入了no
sh test.sh
 请输入yes/no:Ni
 输入了no

for循环语句

Shell脚本入门学习_第13张图片

bash中基本数据类型只有字符串类型,连数值类型都没有(declare -i可强制声明数值类型)。

# test.sh
#!/bin/bash
declare -i sum=0
declare -i i=0
for (( i=0;i<=100;i++  ))
do
	sum=$sum+$i;
done
echo "sum=$sum"
sh test.sh
 sum=5050

Shell脚本入门学习_第14张图片

# test.sh
#!/bin/bash
declare -i sum=0
declare -i i=0
for i in 1 2 3 4 5 6 7 8 9 10
do
	sum=$sum+$i;
done
echo "sum=$sum"
sh test.sh
 sum=55

while循环语句

Shell脚本入门学习_第15张图片

# test.sh
#!/bin/bash
declare -i i
declare -i s
while [ "$i" != "101" ]
do
	s+=i;
	i=i+1;
done
echo "The count is $s"
sh test.sh
 The count is 5050

until循环语句

Shell脚本入门学习_第16张图片

# test.sh
#!/bin/bash
declare -i i
declare -i s
until [ "$i" = "101" ]
do
	s+=i;
	i=i+1;
done
echo "The count is $s"
sh test.sh
 The count is 5050

break continue跳出循环

Shell脚本入门学习_第17张图片

函数

在shell脚本中,函数一定要写在最前面,不能出现在中间或者最后,因为函数是要被调用的,如果还没有出现就被调用,肯定是会出错的。

Shell函数可以当作命令一样执行,它是一个或多个命令的组合结构体。通常,可以为每个功能定义一个函数,该函数中包含实现这个功能相关的所有命令和逻辑。

因为可以组合多个命令,并且定义之后就可以直接在当前Shell中调用,所以函数具有一次定义多次调用且代码复用的功能。

shell函数的定义风格有下面几种:

function func_name {CMD_LIST}
func_name() {CMD_LIST}
function func_name() {CMD_LIST}

Shell脚本入门学习_第18张图片

所有函数在使用前必须定义,必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用

Shell脚本入门学习_第19张图片

函数定义后,可以直接使用函数名来调用函数,同时还可以向函数传递零个或多个参数。

# 不传递参数
func_name
# 传递多个参数
func_name arg1 arg2 arg3

在函数中,那些位置变量将有特殊的意义:

  • $1、$2、$3...:传递给函数的第一个参数保存在$1中,第二个参数保存在$2中…

  • $@$*:保存了所有参数,各参数使用空格分隔

    • 不用双引号包围时,两者没区别

    • 双引号包围时,$@的各个元素都被双引号包围,$*的所有元素一次性被双引号包围

例如,定义一个函数专门用来设置和代理相关的变量:

# proxy_test.sh
#!/bin/bash
proxy_addr=127.0.0.1:8118
function proxy_set {
  local p_addr=$1
  export http_proxy=$p_addr
  export https_proxy=$p_addr
  export ftp_proxy=$p_addr
}

# 调用函数
proxy_set $proxy_addr

# 各代理变量已设置
echo $http_proxy
echo $https_proxy
echo $ftp_proxy

上面在函数定义的代码中使用了local,它可以用在函数内部表示定义一个局部变量,局部变量在函数执行完毕后就消失,不会影响函数外部的环境。

bash proxy_test.sh
 127.0.0.1:8118
 127.0.0.1:8118
 127.0.0.1:8118

另外,函数中可以使用return语句来定义函数的返回值,每当执行到函数内的return时,函数就会终止执行,直接退出函数。在shell中,函数的返回值其实就是退出状态码。

return [N]

如果不指定N,则默认退出状态码为0。

例如:

# return_test.sh
#!/bin/bash
function sayhello {
 [ "$1" ] || { echo "give me a name please!"; return 1; }
 echo hello $1
}

sayhello "junma"; echo $?
sayhello "jinlong"; echo $?
sayhello ; echo $?
sh return_test.sh
 hello junma
 0
 hello jinlong
 0
 give me a name please!
 1

案例:求最值

# test.sh
#!/bin/bash
function my_max()
{
	if [ $1 -gt $2 ]; then
		return $1
	else
		return $2
	fi
}

read -p "请输入数值1:" data1
read -p "请输入数值2:" data2
# 函数调用
my_max $data1 $data2
echo "$data1$data2的最大值为:$?"
sh test.sh
 输入数值1:25
 输入数值2:34
 5和34的最大值为:34

案例:函数分文件

# test.sh
#!/bin/bash
function my_max()
{
	if [ $1 -gt $2 ]; then
		return $1
	else
		return $2
	fi
}
# _sh.sh
#!/bin/bash
# 导入函数
source test.sh

read -p "请输入数值1:" data1
read -p "请输入数值2:" data2

# 调用函数
my_max $data1 $data2
# $? 只有一个字节
echo "$data1和data2的最大值:$?"

使用sh test.sh报错:_sh.sh: line 3: source: test.sh: file not found,但bash test.sh却没有报错。

sh _sh.sh
 _sh.sh: line 3: source: test.sh: file not found
# 脚本运行过程中,没有找到文件test.sh sh不等于bash!
#################################################
bash _sh.sh
 请输入数值1:23
 请输入数值2:32
 23和data2的最大值:32

一般情况下,在Linux中执行一个bash的脚本,建议使用bash去执行,就是为了避免这种奇怪问题的产生。

# _sh.sh
#!/bin/bash
# 导入函数
source ./test.sh
# 给source提供了一个该文件的路径,虽然是相对路径,但bash是可以根据脚本本身执行的位置来找到test.sh的

read -p "请输入数值1:" data1
read -p "请输入数值2:" data2

# 调用函数
my_max $data1 $data2
# $? 只有一个字节
echo "$data1和data2的最大值:$?"

但如果source test.sh写成source ./test.sh,给source提供了一个该文件的路径

命令替换

使用反引号或$()可以执行命令替换。

推荐用 $() 代替 ``

`cmd`
$(cmd)

命令替换是指先执行cmd,将cmd的输出结果替换到$()或反引号位置处。
例如:

id root
echo `id root` 
echo $(id root)

在echo命令执行前,会先执行id命令,id命令的执行结果:

id root
 uid=0(root) gid=0(root) groups=0(root)

所以会将结果uid=0(root) gid=0(root) groups=0(root)替换$(id root)。于是,echo命令开始执行时,命令行已经变成了:

echo uid=0(root) gid=0(root) groups=0(root)

算术运算

$[]$(())或let命令可以做算术运算。
let是单独的命令,不能写在其它命令行中。

a=3
let a=a+1
echo $a

$[]$(())可以写在命令行内部,shell在解析命令行的时候,会对它们做算术运算,然后将运算结果替换到命令行中。

a=33
echo $[a+3]
echo $((a+3))

因为变量替换先于算术替换,所以,使用变量名或引用变量的方式都可以:

a=333
echo $[$a+3]
echo $(($a+3))

管道

管道的用法:

cmd1 | cmd2 | cmd3...

每个竖线代表一个管道。上面命令行表示 cmd1 的标准输出会放进管道,cmd2 会从管道中读取进行处理,cmd2 的标准输出会放入另一个管道,cmd3 会从这个管道中读取数据进行处理。后面还可以接任意数量的管道。

shell管道是shell中最值得称赞的功能之一,它以非常简洁的形式实现了管道的进程间通信方式,个人认为dhell处理文本数据的半壁江山都来自于竖线形式的管道。像其它编程语言,打开管道后还要区分哪个进程写管道、哪个进程读管道,为了安全,每个进程还要关闭不用的读端或写端,总之就是麻烦,而Shell的管道非常简洁,竖线左边的就是写管道的,竖线右边的就是读管道的

例如:

ps aux | grep 'sshd'
root      5049  0.0  0.1 155088  5996 ?        Ss   17:51   0:07 sshd: root@pts/2
root     17562  0.0  0.0 112816   976 pts/2    S+   20:13   0:00 grep --color=auto sshd
root     25830  0.0  0.1 112984  4344 ?        Ss   Jan27   0:00 /usr/sbin/sshd -D

ps(Process Status)命令产生的数据(标准输出)会写进管道,只要管道内一有数据,grep命令就从中读取数据进行处理。

ps查看 Linux 中当前运行的进程的命令。能列出系统中运行的进程,包括进程号、命令、CPU使用量、内存使用量等

示例:

ps -a        # 列出所有运行中/激活进程
ps -ef | grep   # 列出需要进程
ps -aux      # 显示进程信息,包括无终端的(x)和针对用户(u)的进程:如USER, PID, %CPU, %MEM等

运行 Shell 脚本有两种方法

1、作为可执行程序

将上面的代码保存为 test.sh,并 cd 到相应目录:

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 告诉系统说,就在当前目录找。

2、作为解释器参数

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

/bin/sh test.sh
/bin/php test.php

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

Shell echo命令

Shell 的 echo 指令与 PHP 的 echo 指令类似,都是用于字符串的输出。命令格式:

echo string

您可以使用echo实现更复杂的输出格式控制。

  1. 显示普通字符串:
echo "It is a test"

这里的双引号完全可以省略,以下命令与上面实例效果一致:

echo It is a test
  1. 显示转义字符
echo "\"It is a test\""

结果将是:

"It is a test"

同样,双引号也可以省略。

  1. 显示变量

read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量

#!/bin/bash
read name
 Hello World!      
echo "$name is a test"
 Hello World! is a test

以上代码保存为 test.sh,name 接收标准输入的变量,结果将是:

1、编辑sh文件

vi test.sh

(i:插入 | esc:退出insert模式 | wq + 回车:保存退出)

read name   
echo "$name is a test"

2、保存退出

敲击esc, 然后输入:wq ,回车退出

3、添加可执行权限,当然默认就是可执行的

chmod +x test.sh

4、运行文件

sh test.sh

5、输入值,显示结果

sh test.sh
 OK                     #标准输入
 OK It is a test        #输出
  1. 显示换行
echo -e "Is a test one \n" # -e 开启转义
 Is a test one 
# 此处是空行
echo "Is a test two \n"
 Is a test two \n
  1. 显示不换行
#!/bin/sh
echo -e "OK! \c" # -e 开启转义 \c 不换行
 OK! [root@lw ~]# 
echo "It is a test"
 It is a test
  1. 显示结果定向至文件
echo "Hello Linux" > myfile
cat myfile # cat(英文全拼:concatenate)命令用于连接文件并打印到标准输出设备上
 Hello Linux

>重定向输出到某个位置,替换原有文件的所有内容。

>> 重定向追加到某个位置,在原有文件的末尾添加内容。

< 重定向输入某个位置文件。

2> 重定向错误输出。

2>> 重定向错误追加输出到文件末尾。

&> 混合输出错误的和正确的都输出。

  1. 原样输出字符串,不进行转义或取变量(用单引号)
echo "$name\""
 Hello World!"
echo '$name\"'
 $name\"
  1. 显示命令执行结果
echo `date`

注意: 这里使用的是反引号 `, 而不是单引号 '

结果将显示当前日期

Fri Jan 28 10:22:47 CST 2022

该shell脚本中用到了date这个命令,它的作用就是用来打印当前系统的时间。

其实在shell脚本中date使用率非常高。有几个选项常在shell脚本中用

  • %Y表示年

    • 注意%y%Y的区别

    • 比如2022年,对于%y22,而对于%Y2022

    • date "+%Y-%m/%d %H:%M:%S"
       2022-01/29 22:04:33
      date "+%y-%m/%d %H:%M:%S"
       22-01/29 22:04:50
      
  • %m表示月

  • %d表示日期

  • %H表示小时

  • %M表示分钟

  • %S表示秒

date "+%Y-%m/%d %H:%M:%S"
 2022-01/29 22:05:11

-d选项也是经常要用到的,它可以打印n天前或者n天后的日期,当然也可以打印n个月/年前或者后的日期。

date "+%Y-%m/%d"
 2022-01/29
date -d "+2022 year" "+%Y-%m/%d" # 年份加2022
 4044-01/29
date -d "+5 month" "+%Y-%m/%d" # 月份加5
 2022-06/29

星期几也是常用的

date +%w
 6
  1. 输出内容的颜色

echo显示带颜色,需要使用参数-e

echo -e "\033[字背景颜色;文字颜色m字符串\033[0m"

例如:

echo -e "\033[41;37m TonyZhang \033[0m"
# 其中41的位置代表底色, 37的位置是代表字的颜色

注:
1、字背景颜色和文字颜色之间是英文的;
2、文字颜色后面有个m
3、字符串前后可以没有空格,如果有的话,输出也是同样有空格

使用方式:

\e[显示方式;前景色;背景色m
或
\033[显示方式;前景色;背景色m
### 显示方式
0(默认值)、1(高亮)、22(非粗体)、4(下划线)、24(非下划线)、5(闪烁)、25(非闪烁)、7(反显)、27(非反显)

### 前景色
30(黑色)、31(红色)、32(绿色)、 33(黄色)、34(蓝色)、35(洋红)、36(青色)、37(白色)

### 背景色
40(黑色)、41(红色)、42(绿色)、 43(黄色)、44(蓝色)、45(洋红)、46(青色)、47(白色)

控制码:控制字符是打开某种样式,输出完成时需要再关闭样式才能使terminal恢复到原来状态:

printf("\e[32m%s\e[0m\n", "hello world");
\033[0m                       关闭所有属性
\033[1m                    设置高亮度
\033[4m                           下划线
\033[5m                            闪烁
\033[7m                            反显
\033[8m                            消隐
\033[30m----\33[37m         设置前景色
\033[40m----\33[47m         设置背景色

\e[0m将颜色重新置回

特效可以叠加,需要使用“;”隔开,例如:闪烁+下划线+白底色+黑字为 \033[5;4;47;30m闪烁+下划线+白底色+黑字为\033[0m
  1. 标位置等的格式控制
\033[nA  光标上移n行  
\033[nB   光标下移n行  
\033[nC   光标右移n行  
\033[nD   光标左移n行  
\033[y;xH 设置光标位置  
\033[2J  清屏  
\033[K   清除从光标到行尾的内容  
\033[s   保存光标位置  
\033[u   恢复光标位置  
\033[?25l   隐藏光标  
\033[?25h   显示光标
#!/bin/bash
#下面是字体输出颜色及终端格式控制
#字体色范围:30-37
echo -e "\033[30m 黑色字 \033[0m"
echo -e "\033[31m 红色字 \033[0m"
echo -e "\033[32m 绿色字 \033[0m"
echo -e "\033[33m 黄色字 \033[0m"
echo -e "\033[34m 蓝色字 \033[0m"
echo -e "\033[35m 紫色字 \033[0m"
echo -e "\033[36m 天蓝字 \033[0m"
echo -e "\033[37m 白色字 \033[0m"
#字背景颜色范围:40-47
echo -e "\033[40;37m 黑底白字 \033[0m"
echo -e "\033[41;30m 红底黑字 \033[0m"
echo -e "\033[42;34m 绿底蓝字 \033[0m"
echo -e "\033[43;34m 黄底蓝字 \033[0m"
echo -e "\033[44;30m 蓝底黑字 \033[0m"
echo -e "\033[45;30m 紫底黑字 \033[0m"
echo -e "\033[46;30m 天蓝底黑字 \033[0m"
echo -e "\033[47;34m 白底蓝字 \033[0m"
 
#控制选项说明
#\033[0m 关闭所有属性
#\033[1m 设置高亮度
#\033[4m 下划线
echo -e "\033[4;31m 下划线红字 \033[0m"
#闪烁
echo -e "\033[5;34m 红字在闪烁 \033[0m"
#反影
echo -e "\033[8m 消隐 \033[0m "
 
#\033[30m-\033[37m 设置前景色
#\033[40m-\033[47m 设置背景色
#\033[nA光标上移n行
#\033[nB光标下移n行
echo -e "\033[4A 光标上移4行 \033[0m"
#\033[nC光标右移n行
#\033[nD光标左移n行
#\033[y;xH设置光标位置
#\033[2J清屏
#\033[K清除从光标到行尾的内容
echo -e "\033[K 清除光标到行尾的内容 \033[0m"
#\033[s 保存光标位置
#\033[u 恢复光标位置
#\033[?25| 隐藏光标
#\033[?25h 显示光标
echo -e "\033[?25l 隐藏光标 \033[0m"
echo -e "\033[?25h 显示光标 \033[0m"

Shell read 命令

read 命令一个一个词组地接收输入的参数,每个词组需要使用空格进行分隔;如果输入的词组个数大于需要的参数个数,则多出的词组将被作为整体为最后一个参数接收。

测试文件 test.sh 代码如下:

read firstStr secondStr
echo "第一个参数:$firstStr; 第二个参数:$secondStr"

Vim删除所有内容

命令为:ggdG

其中,gg为跳转到文件首行;dG为删除光标所在行以及其下所有行的内容。

再细讲,d为删除,G为跳转到文件末尾行。

执行测试:

sh test.sh 
 test1 test2 test3 test4 test5 test6
 第一个参数:test1; 第二个参数:test2 test3 test4 test5 test6

实例,文件 test.sh:

read -p "请输入一段文字:" -n 6 -t 5 -s password
echo -e "\npassword is $password"

参数说明:

  • -p 输入提示文字
    • read -p 选项类似echo的作用
  • -n 输入字符长度限制(达到6位,自动结束)
  • -t 输入限时
  • -s 隐藏输入内容
sh test.sh 
请输入一段文字:
password is 1asesw

Shell tar命令

tar命令是Linux中常用的解压与压缩命令

tar [-cxtzjvfpPN] 文件与目录 ....
参数:
-c :建立一个压缩文件的参数指令(create 的意思);
-x :解开一个压缩文件的参数指令!
-t :查看 tarfile 里面的文件!
特别注意,在参数的下达中, c/x/t 仅能存在一个!不可同时存在!
因为不可能同时压缩与解压缩。
-z :是否同时具有 gzip 的属性?亦即是否需要用 gzip 压缩?
-j :是否同时具有 bzip2 的属性?亦即是否需要用 bzip2 压缩?
-v :压缩的过程中显示文件!这个常用,但不建议用在背景执行过程!
-f :使用档名,请留意,在 f 之后要立即接档名喔!不要再加参数!
   例如使用『 tar -zcvfP tfile sfile』就是错误的写法,要写成
   『 tar -zcvPf tfile sfile』才对喔!
-p :使用原文件的原来属性(属性不会依据使用者而变)
-P :可以使用绝对路径来压缩!
-N :比后面接的日期(yyyy/mm/dd)还要新的才会被打包进新建的文件中!
注意:
1、--exclude=file1 而不是 --exclude file1
2、要排除一个目录是--exclude=dir1而不是--exclude=dir1/

范例一:将整个 /etc 目录下的文件全部打包成为 /tmp/etc.tar

tar -cvf /tmp/etc.tar /etc # 仅打包,不压缩!
tar -zcvf /tmp/etc.tar.gz /etc # 打包后,以 gzip 压缩
tar -jcvf /tmp/etc.tar.bz2 /etc # 打包后,以 bzip2 压缩
# 特别注意,在参数 f 之后的文件档名是自己取的,我们习惯上都用 .tar 来作为辨识。
# 如果加 z 参数,则以 .tar.gz 或 .tgz 来代表 gzip 压缩过的 tar file
# 如果加 j 参数,则以 .tar.bz2 来作为附档名
# 上述指令在执行的时候,会显示一个警告讯息:
# 『tar: Removing leading `/' from member names』那是关于绝对路径的特殊设定。

范例二:查阅上述 /tmp/etc.tar.gz 文件内有哪些文件?

# 由於我们使用 gzip 压缩,所以要查阅该 tar file 内的文件时,就得要加上 z 这个参数了!这很重要的!
tar -ztvf /tmp/etc.tar.gz # 参数t查看tar file里面的文件!

范例三:将 /tmp/etc.tar.gz 文件解压缩在 /usr/local/src 目录下

cd /usr/local/src
tar -zxvf /tmp/etc.tar.gz # 参数x——解压
# 在预设的情况下,我们可以将压缩档在任何地方解开的!以这个范例来说,我先将工作目录变换到 /usr/local/src 底下,并且解开 /tmp/etc.tar.gz,则解开的目录会在 /usr/local/src/etc
# 另外,如果您进入 /usr/local/src/etc,则会发现,该目录下的文件属性与 /etc/ 可能会有所不同

范例四:在 /tmp 底下,只将 /tmp/etc.tar.gz 内的 etc/passwd 解开

cd /tmp
tar -zxvf /tmp/etc.tar.gz etc/passwd
# 可以通过 tar -ztvf 来查阅 tarfile 内的文件名称,如果单只要一个文件,就可以通过这个方式来下达!
# 注意!etc.tar.gz 内的根目录 / 是被拿掉了

范例五:将 /etc/ 内的所有文件备份下来,并且保存其权限

tar -zxvpf /tmp/etc.tar.gz /etc
# -p 的属性是很重要的,尤其是当您要保留原本文件的属性时!

范例六:在 /home 当中,比 2005/06/01 新的文件才备份

tar -N '2005/06/01' -zcvf home.tar.gz /home

范例七:备份 /home,/etc ,但不要 /home/dmtsai

tar --exclude /home/dmtsai -zcvf myfile.tar.gz /home/* /etc

范例八:将 /etc/ 打包后直接解开在 /tmp 底下,而不产生文件

cd /tmp
tar -cvf - /etc | tar -xvf -
# 这个动作有点类似 cp -r /etc /tmp 依旧是有其有用途的!
# 要注意的地方在于输出档变成 - ,而输入档也变成 - ,又有一个 | 存在
# 这分别代表 standard output, standard input 与管线命令

你可能感兴趣的:(小白学习笔记,linux,bash)