shell基础编程
shell的绪论
-
必须指定使用何种解析器解析,实际上linux中所有解释型的语言都会指定解析器。且必须放到首位。
#!/bin/bash # 这是bash脚本的解析器
#!/usr/bin/python3 # 这是python脚本的解析器,使用which python3可以查看当前linux使用的python3的具体路径
- 关于注释:没有多行注释,只有当行注释,标识符为#
- 执行脚本通常经过两步:赋予权限,执行。使用chmod 赋予脚本的执行权限;使用bash ./脚本名来执行脚本。
- 脚本的命名通常使用_来表示(使用小驼峰也无所谓)。linux中无所谓后缀名的概念,通常在脚本后面的.sh的后缀用来告诉用户这是一个shell脚本
shell中的标准输入和输出
标准输入
-
使用格式:read [-options] [variable...]
- 其中-options表示可选参数,variable为变量名,可以是多个,用于接收多个参数。
- 其中常用的参数为:
a |
表示输入的是数组 |
p |
表示后面紧跟的是提示,而不是变量名。注意,该参数永远要放到最后 |
s |
静默输入,表示输入的内容不会在控制台显示,用于输入密码 |
-
输入的内容传到指定的参数中
read test # 将输入的内容传递到参数test中
-
给出提示,同时将输入的内容传入到变量中
read -p '请输入内容->' yhb # 给出提示,同时将输入的内容存到yhb变量中。
-
将输入的存到数组中
read -a myArray # 将输入的数组存到myArray中。
-
实现静默输入
read -sp '请输入您的密码-》' passwd
echo ${passwd}
标准输出
-
输出的内容不换行(默认的是换行输出内容)
-
使用-e选项,允许输出转移符。
echo -e "yhb\n"
-
-
输出带颜色的文字
echo -e "\e[1;31mThis is red text\e[0m"
\e[1;31m 表示将文字的颜色输入为红色
\e[0m表示红色字体结束,换回正常颜色
重定向
- >> 表示追加
- > 表示覆盖
- < 表示读取文件
- sh c.sh 1>>right.log 2>>/dev/null # 将正确的数据追加到right.log中,将错误的信息输出到/dev/null中,实际上就是丢弃。
- command >>file 1>&2 将无论正确错误的数据都追加到file中
shell中的变量
shell中变量的种类
- 环境变量,变量名通常是大写的
- 位置变量,主要用于向函数或脚本传递参数。位置变量从$1开始,一直传递到$9。之后可以传递更多的变量,但是很遗憾我忘了
- 特殊变量
$? |
上一条命令是否执行成功,执行成功返回0,不成功非0 |
$0 |
返回当前脚本名 |
$# |
返回当前所有位置变量的个数 |
$@ |
返回当前所有的位置变量 |
变量的声明与获取
-
变量的声明
- shell中是没有所谓的数据类型的概念,它做运算的能力特别差,只适合做胶水型语言,将各种linux或者其他的命令组合起来
- 一般格式为:变量名=值 # 注意等号两边不要用空格
- eg: name=yhb
-
变量的获取
- 通常我们使用$来获取某个变量的值
- eg: ${name} # 其中{}是可选的,为了标识表示变量名的字符串是唯一的变量名,建议加上
关于变量中的特殊符号
- 单引号'':表示这是一个纯正的字符串,就是中间的特殊字符也会被解析为字符输出而不是被解析为相应的转意符
- 双引号"":表示这是一个字符串,但是中间的特殊符号会被解析为转意符,比如${name}会调用name变量的参数,而在单引号中被直接输出的。引号不仅标识了一个字符串,也标识了字符串是一个整体。
- 反引号``:表示这是一条linux命令,会被执行,并将执行结果赋给变量。eg:result=`ls /` # 执行后result的值是 ls / 这条命令执行的结果。通常我们建议使用$()来替代``
环境变量
- 在linux启动时,会经存储在/etc/profile和当前用户的~/.bash_profile,~/.bash_login,~/.profile等文件中的环境变量进行初始化。当我们登录用户时,会依次初始化当前用户家目录下的配置文件中的变量;如果进行修改除非注销用户重新登录。另外就是souce 配置文件来实现修改的环境变量生效。
- 当用户的~/.bashrc只对当前用户的bash shell有效。
- /etc/profile是对所有用户有效,~/.bash_profile 可以理解为当前用户的profile文件,只对当前用户有效
shell中字符串的处理
字符串的长度
# 在bash中通常表示的是长度
name="huabingood"
echo ${#name} # 输出name这个变量的字符长度,对中文是不准的。
截取字符串
- 第一数字表示从第几位开始截取,第二个数字表示截取几位,中间使用英文的冒号分割
-
实例:
name="huabingood"
echo ${name:4} # 输出结果是ingood,从第4位开始截取,截取到最后。是从0开始的。
echo ${name:1:3} # 输出结果是uab,从第一位开始截取,截取3位
echo ${name:(-6),1} # 输出结果是i 从倒数第六位开始,往后截取一位。倒数的话是从-1开始计算的
是否包含和替换子串
建议通过grep =~ 以及sed来实现
shell中的语句
if语句
-
if语句的判断条件
- 数字的比较
-eq |
相等 |
-ne |
不相等 |
-ge |
大于等于 |
-le |
小于等于 |
-gt |
大于 |
-lt |
小于 |
- 字符串的比较
= |
字符串相等,等号两边必须与空格存在,否则被解析为赋值 |
!= |
字符串不相等 |
-n |
字符串不为空 |
-z |
字符串为空 |
=~ |
模糊匹配,比用写通配符,效率如同java中的contain |
- 文件的比较
-f |
为文件 |
-d |
为文件夹 |
-e |
文件存在 |
例如: if [ ! -f "${filePath}" ] # 注意方括号两边是要有空格的。如果是变量的话一定要加引号,引号不仅表示字符串,也标识一个整体;本例中表示如果这个文件不是文件。
-
if语句使用的一般格式以及示例
-
一般格式
注意[]两边一定要空格,各种test命令也一定要空格
-
1 if [ test command ] ;then 2 3 command1 4 5 elif;then 6 7 command2 8 9 else 10 11 command3 12 13 fi
|
- 示例,判断上一条命令是否执行成功
1 if [ $? -eq 0 ];then # 这里的[]里面的各个部分都必须有空格 2 3 ehco "执行成功" 4 5 else 6 7 echo "执行失败" 8 9 fi # shell中的各种语法都是成对出现的 |
case语句
语法上比较特殊的地方是:每句的结尾使用两个分号;整段的结果使用esca结尾
1 #!/usr/bin/env bash 2 3 4 5 # read -p的命令在IDEA中存在写问题 6 7 read -p "请输入您的名字:" name 8 9 10 11 case ${name} in 12 13 "yhb") 14 15 echo "hyw" 16 17 ;; 18 19 "hyw") 20 21 echo "yhb" 22 23 ;; 24 25 * ) # 这个表示适配其他一切没有适配到的输入 26 27 echo "You input ware wrong!" 28 29 ;; 30 31 esac |
循环语句
-
for循环
-
一般格式
通常情况是使用java的循环控制流程是最好的。
注意for循环的开始时是do,结束是done
-
1 for (i=0;i<10;i++) 2 3 do 4 5 echo "hahha" 6 7 done 8 9 10 11 -- 方式二 12 13 for i in `ls /` 14 15 do 16 17 echo $i 18 19 done |
-
while循环
-
一般格式
例如我们通过while循环来读取一个文件里的内容
-
1 filePath=/home/huabingood/abc.txt 2 3 4 5 while read line 6 7 do 8 9 echo ${line} 10 11 12 13 done < ${filePath} # 注意这里的符号,是<,表示读取,千万不要写成追加了。 |
-
循环中的跳出
- 使用break跳出循环。但是不同的是java中的break是跳出所有循环,而shell中需要break n才行。比如需要跳出2层循环,就必须写break 2.
- 使用continue表示跳出当前
函数
函数的定义
- 使用function关键字
- 在函数名后面添加(),注意这里面不是加形参的,只是单纯的一个括号
- 函数体使用{}包围
1 function functionName(){ 2 3 # I'm a function 4 5 } |
函数的使用
-
调用
- 就像正常的shell命令一样使用即可
- 如果传递参数的话,就像使用shell脚本一样,在后面跟参数即可,参数之间使用空格隔开
-
返回值
- 没有返回值。如果函数中的变量只在函数中有用,函数外的变量依照先后顺序左右范围一次降低,越靠近文件的顶部,作用域越广。
- 如果实在想使用返回值:可以重新复制给一个高级别的变量,或者使用echo输出
-
执行状态
- 理论上执行状态才是返回值,但是0和非零两种状态,0是指该函数运行成功,非零表示该函数运行失败,使用$?接受执行参数。(这个状态和我们的业务状态正好相反,在数据库中0是假,1是真)
- 一个小练习
1 #!/usr/bin/env bash 2 3 4 5 ###################################### 6 7 ## 使用ls -al 获取/tmp下的所有文件 8 9 ## 输出获取的文件夹 10 11 ###################################### 12 13 14 15 command="ls -al /tmp" 16 17 18 19 # 判断是否是文件夹 20 21 function isDir(){ 22 23 file="/tmp/${1}" 24 25 if [ -d ${file} ];then 26 27 echo "$1" 28 29 fi 30 31 } 32 33 34 35 # 获取所有路径,并判断是否是文件 36 37 function getFileName(){ 38 39 # 执行如遇 40 41 result=$( ${command} ) 42 43 44 45 # 验证上面的语句是否执行成功 46 47 if [ $? -ne 0 ];then 48 49 echo "出错了!" 50 51 exit 1 52 53 fi 54 55 56 57 # 执行成功的话,就将文件输出 58 59 for i in "${result}" 60 61 do 62 63 # 这种写法会将整个ls获取的内容当成一个整体 64 65 fileName=$(echo "${i}" | awk -F " " '{print $9}') 66 67 68 69 for j in ${fileName} 70 71 do 72 73 isDir "${j}" 74 75 done 76 77 done 78 79 } 80 81 82 83 # 执行命令 84 85 getFileName |
数组
这个算是shell中唯一的高级点的数据结构
特点与定义
- 数据类型不限
- 使用括号来表示一个数组,不用特别的声明
-
数组的下标是从0开始的
数组的定义与使用
1 #!/usr/bin/env bash 2 3 4 5 # 数组的声明 6 7 myArray=("yhb" "hyw" 1) 8 9 # 数组单个元素的调用 10 11 echo "${myArray[1]}" 12 13 # 数组变量的修改 14 15 myArray[1]="zjq" 16 17 echo "${myArray[1]}" 18 19 20 21 ## 数组的遍历--方式一 22 23 echo "通过遍历数组元素直接访问" 24 25 for i in ${myArray[@]} 26 27 do 28 29 echo $i 30 31 done 32 33 34 35 ## 数组的便利--方式二 36 37 echo "通过便利数组下标访问:" 38 39 for (( i=0;i<${#myArray[@]};i++ )) 40 41 do 42 43 echo "${myArray[$i]}" 44 45 done |
shell中的日期的通常用法
日期的加减
-
输出指定当前指定格式的日期
date +"%Y%m%d" # 输出当前年月日
date +"%H:%M:%S" # 输出当前时分秒
-
日期的加减
# 输出一天以前
date -d "-1 day"
date -d "1 day ago"
# 输出一月以前
date -d "-1 month" +"%Y%m%h"
date -d "1 month ago" +"%Y%m%d %H:%M:%S"
# 输出十天以后
date -d "10 day "
时间戳与格式化日期相互转换
-
时间戳转日期
date -d @"时间戳" +"%Y%m%s"
例如:date -d @"1511234976" +"%Y%m%d %H:%M:%S"
-
如何将当前或指定时间转换为时间戳
date +"%s" # %s(这个是小写的s),可以输出unixs时间戳
附加知识
- UTC与GMT时间是很相似,都是以格林尼治天文台为标准的。UTC时间是原子钟的震荡为1秒,GMT时间按照地球绕月的运行来计算时间。
- unix 时间戳:计算从1970年1月1日0点0分开始的距今的秒值(注意不是毫秒)。起始时间是0时区的,通常系统会根据调用返回这个值。
脚本编写与调试小技巧
如何调试脚本
- 使用 bash -n 脚本.sh # 用于检验当前脚本是否存在语法问题,并不会运行
- 使用bash -x 脚本.sh # 执行脚本,并输出执行过程中的各种信息
指定脚本的编码格式
-
并不是所有的Terminal都是UTF-8编码的,所以为了防止乱码,必须在脚本中指定编码格式
export LANG="en_US.UTF-8"
export LC_ALL="en_US.UTF-8"
后台运行脚本
- nohup bash script.sh # 表示在当前终端运行,会占用当前终端,但是关掉当前终端并不会杀死进程
- nohup bash script.sh & # 这里的&表示进程在在后台运行,不会占用当前终端,也就是说该终端也是可以继续运行别的程序和命令,这就有点多线程(实际上是多进程)的意味了。
实现多进程
- Bash实际上是写不出来多线程的,只能有多进程,多个程序并行运行,进程之间是无法进行通信的。
- 在命令后面是有&来表示将该命令放到后台运行,于此同时,可以运行其他的命令。
- command &
在脚本中引用其他的脚本
- 使用source命令,只要source一下文件即可。
-
命令如下:
在main.sh中使用如下命令:
source control.sh # 即可使用control.sh中的所有变量与函数
一些有用的命令
-
查看文件
- ls -al # 查看所有文件,即使是隐藏文件也不被显示出来
- ls -clh # 将文件安装创建时间进行排序,并显示文件的大小。其中c表示创建时间,l表示显示详细信息,h表示文件的大小会跟上单位
- ls -clhr # 将安装文件创建时间降序排列
- ls -hlS # 按照文件大小排序。注意是大写的S。通常这里只显示文件的大小,文件夹统一按照4K计算
-
创建软连接(可以理解为windows下的快捷方式)
-
创建软(硬)连接
- ln -s source target # 创建软连接
- ln source target # 创建硬链接
-
软硬链接的区别
- 软连接只是将文件的唯一标识复制了一份,一旦源文件删除了,软连接也就找不到了。硬链接实际上是做了文件标识的备份,即使其中一个被删除了,也能通过另外一个迅速的找到。
- 建立软链接就是建立了一个新文件。当访问链接文件时,系统就会发现他是个链接文件,它读取链接文件找到真正要访问的文件。
- 无论软硬链接,一但修改都是同步性的修改。
-
删除软连接
- rm 软连接文件名
- 如果删除的是源文件名称的话,会导致软链接失效,硬链接只能通过链接访问,源文件无法访问。
-
-
文件查找
-
根据文件名查找
- find -name key # 这里的key可以是文件名中的关键字。也可以使用*来进行匹配
- find /路径 -name key # 表示在指定的路径中查找含有关键字的文件名。如果不指定路径的话就是在当前路径下寻找
-
根据文件大小查找
-
find -size 文件大小
- find -size +100m 寻找当前路径下大于100M的文件
- find -size -100k 寻找当前路径下小于100k的文件
- find /home -size +10G 寻找当前路径下大于10g的文件
-
-
grep过滤
- grep "字段" 文件名1 文件名2 文件名3 # 在多个文件夹中查找
- grep -v haha # 表示查找不包含haha的内容
-
-
磁盘大小查看
- df -h 查看各个硬盘的大小
-
查找文件的大小
- du -h # 查看路径下所有文件的带下
- du -h --max-depth=2 /tmp # 查找/tmp文件夹下,文件层级为2的所有的文件大小
- du -hs * | sort -hr | head -10 # du中的s表示只计算文件,文件夹的内容不管。du -hs 这条命令会显示当前文件夹的大小。du -hs * 会显示当前文件夹内第一层级的文件的大小。
-
字符处理相关函数
-
sort
-
参数
- -h 表示按照人们的思考方式排序,比如即使8K在数值上小于1G,但是1G仍然会在前面
- -n 表示按照数字排序,比如不会出现1,11,2,22 这种情况
- -r 表示降序
- -o 表示输出到一个文件中,这是后重定向是没有用的
-
实例代码
- sort abc.txt
- sort -h abc.txt # 表示按照人们能够接受的方式排序
- sort -h abc.txt -o aa.txt # 将abc.txt中的内容排序后重定向到aa.txt中
-
-
uniq
- 注意只能识别相邻的两个重复值,如果不相邻的话,是无法识别未被重复的。因此在使用uniq前必须使用sort进行排序
-
实例代码
- sort abc.txt | uniq
- sort abc.txt | uniq -c # 找出各行重复的个数
- sort abc.txt | uniq -d # 显示重复的行
- sort abc.txt | uniq -u # 找出不重复的行
-
tr命令:多用于替换特殊字符(建议使用sed)
- cat abc.txt | tr -d "\r" # 替换掉Windows文件传入linux中多出来的^M 。我到现在都没有弄懂dos2unix命令为什么没有用。
-
统计行数问题
-
参数
- -l 统计行数
- -w 统计单词数
- -c 统计字符数
-
代码示例
- wc -l fileName
- 对于中文只能统计行数,单词数和字符数是没有办法统计的
-
-
-
basename和dirname
- 区别如下:
给定路径 |
basename结果 |
dirname结果 |
/home/huabingood/abc |
abc |
/home/huabingood |
/home/huabingood/abc.txt |
abc.txt(或abc) |
/home/huabingood |
/usr/ |
/ |
usr |
usr |
usr |
. |
/usr |
/ |
usr |
- basename是取路径的最后一个路径,dirname是取最后一个路径的前面的所有路径
- basename如何清除后缀名: basename /huabingood/abc.txt .txt # 如果忽略某个后缀,只要空格后面加后缀的形式即可。实际上只要字符跟末尾匹配,就能忽略掉。basename /huabingood/abctxt txt # 结果获取的就是abc了。
sed和awk
-
sed
- 可以理解为命令行的vim
-
常用方法
- sed [-nefri] 'sed命令' 输入文本
- 对nefri的解释
i |
直接进行修改 |
n |
只将被修改的部分输出出来 |
-
sed中的增删改查
-
增
sed -i '1a drink tea' a.txt
sed -i '1,3a drink tea' a.txt
sed -i '3,$a drink tea' a.txt # 从第三行到最后的一行的每一行的后面添加一行
-
删
sed -i '1d' a.txt # 删除第一行
sed -i '2,$d' a.txt # 从第二行到最后一行都删掉
sed -i '/my/d' a.txt # 将含有my字符的行删掉
-
改
- 直接修改
-
行内替换(重点)
sed -i 's/ruby/hadoop/g' a.txt # 将所有的行的ruby替换为hadoop
sed -n '1,20s/ruby/hadoop/g' a.txt # 将1到20行的ruby替换成hadoop
-
1 #!/usr/bin/env bash 2 3 4 5 # 修改自己的自己,将name的值替换为age的值 6 7 8 9 age="18" 10 11 name="huabingood" 12 13 14 15 sed -i "s/${name}/${age}/g" $0
|
-
查
使用grep或者=~即可,不建议再学习
sed -n '1p' a.txt # 显示第一行
sed -n '/ruby/p' a.txt # 显示含有ruby的行
-
awk
-
根据使用频率,建议牢记如下命令
-
按照指定的分割符进行分割字符串,并输出第一个和最后一个
cat a.txt | awk -F ",." '{print $1,$NF}' # -F 后面跟着指定的分割符,$1表示分割的参数中的第一个,$NF表示分割参数的最后一个
-
输出文本中第三行和第五行的第一列,第二列,以及最后一列
cat a.txt | awk "NR==2,NR==3" '{print $1,$2,$NF}'
-
-