Shell脚本:快速入门

常用指令

linux命令实在有点多,目前感觉比较好的学习方法是先了解和整理一下命令的分类,了解每个分类下一些常用命令功能,然后根据实际需求去查具体用法。使用-h是一种方式,不过有些可能过于抽象,可以使用一些专门的网站查询命令用法,比如:www.linuxcool.com等。

通常执行一个sh脚本只需要运行如下命令:./test.sh,这种方式往往会报没有运行权限的错误,这时候可以:1)进行授权chmod a+x ./test.sh;2)换个方式:sh ./test.sh。后者还可以通过sh +x ./test.sh来查看脚本执行过程,方便调试。

文件(夹)

指令描述示例
pwd打印当前文件路径pwd
file确定类型的文件file .
ls
ll
列出目录下的文件,ll是ls -l的别名
-a:显示指定路径中的所有文件,包括隐藏文件
-l:显示文件的详细信息,包括文件类型,权限,所属用户,所属用户组,文件大小,上一次修改时间等
-h:文件大小以KBytes为单位显示
-S:按照文件大小顺序显示,默认从大到小;若要从小到大,可使用-Sr
-r:将文件以相反次序显示(ls默认依英文字母a-z次序)
-t:以最后一次修改时间排序
-d:将目录象文件一样显示,而不是显示其下的文件,只显示当前文件夹
-R:递归列出所有目录的文件
cd改变目录cd . .#进入上级目录
dirname获取指定路径的父目录dirname $(pwd)#返回的是指定路径父目录的绝对路径
mkdir创建目录mkdir ./test
rmdir删除空目录rmdir ./test
rm删除文件(夹)rm -r ./test
chmod设置访问权限chmod a+x test.sh
chmod 777 test.sh
ln链接文件目录
cmp比较二进制文件
diff比较两个文件的差异
mv移动文件/目录mv src dst
cp复制文件/目录cp src dst
chgrp变更目录文件所属组
chown变更文件目录的所有者
dd复制文件并格式化
touch修改文件时间或新建文件
find查找文件
locate快速查找文件
which查找可执行程序文件
whereis查找执行程序的相关文件

关于文件查找命令哪家强,请看这篇:Linux的五个查找命令

文件内容

指令描述示例
nl添加行号
echo回显变量,几个常用参数:
-n:表示内容不换行,默认会换行
-e:表示激活转义字符
echo $SHELL
printf格式化输出,几个常用参数:
%s:字符串占位符
%-ns:n个字符的字符串占位符,-表示左对齐,默认右对齐
%c:字符占位符
%d:整数占位符
%f:浮点数占位符,默认小数点后6位
%-n.mf:浮点数占位符,- 表示左对齐, n 表示显示长度, m 表示小数的位数
printf "%s %d\n" "Hello World" 123
cat打印文件所有内容至控制台cat test.txt
more打印文件内容至控制台,如果内容超过阈值则部分显示,通过enter查看更多more test.txt
less逐页显示文件内容
head显示文件开头内容
tail显示文件后面内容,在查看日志时使用比较多tail -f /usr/local/nginx/log/error.log
od查看特殊格式的文件内容
grep查找文本内容,配合管道符使用的场合较多
-E:正则匹配,支持多个-e
-ve:正则匹配取反,即不包含
-a :将 binary 文件以 text 文件的方式搜寻数据
-c :计算找到 ‘搜寻字符串’ 的次数
-i :忽略大小写的不同,所以大小写视为相同
-n :顺便输出行号
-v :反向选择,亦即显示出没有 ‘搜寻字符串’ 内容的那一行
--color=auto :可以将找到的关键词部分加上颜色
patch修补文件

cat配合<<EOF可以实现多行文本输入到指定文件中:

# 将多行文本添加到文件末尾,如果>>改为>就变成覆盖了
cat >> ./text.txt <<EOF
line1
line2
line3
EOF

echo转义字符:

  • \n 换行
  • \r 回车
  • \t 制表符 tab
  • \b 退格
  • \v 纵向制表符

vi/vim

操作描述示例
模式切换编辑模式
命令模式
插入模式
按o:进入编辑模式
按ESC:进入命令模式
按i:进入插入模式
文件保存:w保存文件但不退出vi 编辑
:w! 强制保存,不退出vi 编辑
:w file将修改另存到file中,不退出vi 编辑
:wq保存文件并退出vi 编辑
:wq!强制保存文件并退出vi 编辑
:q不保存文件并退出vi 编辑
:q!不保存文件并强制退出vi 编辑
:e!放弃所有修改,从上次保存文件开始在编辑

账号相关

指令描述示例
whoami查看当前用户whoami

进程管理

指令描述示例
psProcess Status,打印进程信息ps
kill杀进程kill -9 <pid>

任务调度

指令描述示例
jobs查看当前正在执行的脚本任务jobs
fb/bg切换任务到控制台前台/后台bg 1 #1为调用jobs命令时任务看到的任务序号
nohupno hangup,忽略HUP信号,让指令在当前会话结束或者当前用户退出登录后可以继续运行;并且将当前指令的输出内容添加到当前目录或者home目录的"nohup.out"文件中nohup ping baidu.com
&让当前指令在后台运行,不会阻塞当前用户交互。并且会在指令执行完成后,立即返回到前台,同时输出后台执行时的PID和指令输出的内容cat nohup.out &

来源:Linux中的nohup和& | Linux指令后台执行

ctrl+z可以挂起(暂停)当前正在执行的的任务

linux软链接

创建软链接命令: ln -s [dir1] [dir2]

  • dir1是真实的文件夹
  • dir2是dir1的软链接

软链接可以理解为,dir2是dir1的快捷方式,进入了dir2,就会自动进入dir1。

例子:

 ln -s /home/datasets/JHMDB /home/MOC_detector/data/JHMDB 

真正的JHMDB数据集保存在/home/datasets/JHMDB中,/home/MOC_detector/data/JHMDB 是 /home/datasets/JHMDB的软链接。

软链接不仅对文件夹适用,对文件也同样适用。

常用场景

环境变量

配置文件

  1. zsh:文件位于~/.zshrc
  2. Mac bash:文件位于~/.bash_profile
  3. Linux bash:文件位于~/.bashrc

进入编辑

通常需要管理员权限才能修改

通过vim修改

打开配置文件:

sudo vi ~/.bash_profile

设置变量:

# 方式1
export http_proxy=http://127.0.0.1:1087
export https_proxy=$http_proxy

# 方式2
export http_proxy=http://127.0.0.1:1087 https_proxy=http://127.0.0.1:1087
通过echo追加修改
echo  'export PS1="\[\033]2;\h:\u \w\007\033[33;1m\]\u \033[35;1m\t\033[0m \[\033[36;1m\]\w\[\033[0m\]\n\[\e[32;1m\]$ \[\e[0m\]"' >> ~/.bash_profile

使修改生效

修改完执行source才能在当前会话立即生效

# 对于bash
source ~/.bash_profile

hosts

位于/etc/hosts

系统信息

系统判断

#!/bin/bash

uNames=`uname -s`
osName=${uNames: 0: 4}
if [ "$osName" == "Darw" ] # Darwin
then
	echo "Mac OS X"
elif [ "$osName" == "Linu" ] # Linux
then
	echo "GNU/Linux"
elif [ "$osName" == "MING" ] # MINGW, windows, git-bash
then 
	echo "Windows, git-bash" 
else
	echo "unknown os"
fi

软件是否安装

思路一:使用pm软件查询指定软件是否安装
示例:使用rpm判断是否安装了flutter

#!/bin/bash
check_result=`rpm -qa | grep "flutter"`
echo "check flutter result: $check_result"

不推荐,各个操作系统pm软件不同,很难做到通用

思路二:直接运行软件,检查输出是否符合预期

#!/bin/bash
check_result=$(flutter --version | grep flutter)
echo "check flutter result: $check_result, code:$?"
if [[ $check_result =~ "flutter" ]];
then
    echo "flutter has installed."
else
    echo "flutter not installed"
fi

check_result=`git --version | grep git`
echo "check git result: $check_result, code:$?"
if [[ $check_result =~ "Git" ]];
then
    echo "git has installed."
else
    echo "git not installed"
fi

在我的设备中,没有安装flutter,安装了git,运行效果如下:

line 7: flutter: command not found
check flutter result: �� code:1
flutter not installed
check git result: git version 2.32.1 (Apple Git-133), code:0
git has installed.

多了一行报错的命令,这不是我们预期的效果,改进方法如下:

check_result=$(flutter --version 2>&1)
echo "check flutter result: $check_result, code:$?"
if [[ $check_result =~ "flutter" ]];
then
    echo "flutter has installed."
else
    echo "flutter not installed"
fi

输出效果:没有报错信息了

check flutter result: �� code:127
git has installed.

补充知识点:
0 – stdin (standard input,标准输入)
1 – stdout (standard output,标准输出)
2 – stderr (standard error,标准错误输出)
希望执行某个命令,又不希望在平面上显示结果,可以将输出重定向到/dev/null,如 :echo ‘hello' > /dev/null

简单轮询逻辑

  • 实现一个简单的轮训逻辑:
while ((1)) do
> dumpsys cpuinfo
> sleep 0.01    //默认单位为s,也支持m和h
> done

命令行技巧

  • 输入\可以换行;

编写脚本

掌握上述基本命令,我们可以通过命令行实现我们想要的各种功能,很多重复性流程工作还可以进一步编成脚本,下面介绍如何编写shell脚本。
先查看下自己系统默认的shell:

echo $SHELL

输出:
/bin/bash

查看系统支持的shell:

cat /etc/shells

输出:
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash

第一个脚本

下面看一个最简单的shell脚本(test.sh):

#! /bin/bash
echo "hello world shell"

我们运行这个脚本:

bash test.sh

输出:
hello world shell

分析一下:

  • 第一行:#! /bin/bash
    指定这个脚本用什么解释器执行,就是前面我们提到过的系统支持的shell。#!是一个约定的标记,固定写法。
  • 第二行:脚本内容
    这部分就是你要实现的工作内容,本示例中仅回显一个字符串

关于授权
如果执行上述命令报错:bash ./test.sh: Permission denied,则表示用户没有脚本的执行权限,进行授权即可:chmod +x test.sh

脚本参数:

  • $0 获取当前执行脚本的文件名
  • $n 获取脚本的参数,n=1…9 10以后就要${10}
  • $# 脚本后边的总参数
  • $* 获取当前脚本的所有参数"$1 $2 $3"
  • $@ 获取当前脚本的所有参数,加了双引号"$@" “$1” “$2” “$3”
  • $? 获取上一个命令的执行状态的返回值,成功为0 不成功非0
  • $$ 获取当前执行shell脚本进程的pid
  • $! 获取上一个在后台工作的pid
  • $_ 获取在次之前执行命令或脚本的最后一个参数

符号和运算符

符合含义示例
$变量引用符,用于引用变量
$1命令参数,
$0是脚本本身的名字;
$1表示第一个参数,比如bash test.sh a b c,那么$1就是a
  • -ge 大于等于
  • -le 小于等于
  • -gt 大于
  • -lt 小于
  • -eq 等于
  • -ne 不等于
算数比较符
  • >= 大于等于
  • <= 小于等于
  • > 大于
  • < 小于
  • == 等于
  • != 不等于
算数比较符
(())一种数学计算命令,它除了可以进行最基本的加减乘除运算,
还可以进行大于、小于、等于等关系运算,以及与、或、非逻辑运算
if (( $a == $b ))
  • &&
  • `
或</li><li>!` 非

变量

申明变量

定义变量无需特定关键词,直接赋值即可:

# 示例:变量的基本使用

# 定义变量
name=hello

# 使用变量
echo $name
echo ${name}

bj=world
${#bj} #按照字符返回长度
${bj:offset} #从offset位置开始切割到结尾
${bj:offset:length} #从offset位置开始切割指定的长度

删除变量

unset name

只读变量

# 定义只读变量,只读变量不可删除
readonly age=10

# 尝试修改只读变量将会报错:age: readonly variable
# age=11

键盘输入

示例:从键盘接收输入,并赋值给变量

#!/bin/bash
# 带提示语
read -p "Please input a number:" a
# 不带提示语
read b
if (( $a == $b ))
then
    echo "a和b相等"
fi

数字运算

(1) let var=算术运算表达式
(2) ((var=算术运算表达式)) 和上面等价,最高效
(3) var=$[算术运算表达式]
(4) var=$((算术运算表达式))
(5) var=$( expr arg1 arg2 arg3 ... )  # 注意两边的空格
(6) declare -i var = 数值
(7) echo '算术表达式' | bc

数学计算要用$((…))或者$[...]括起来,记得前面要加上符号$。

a=1
b=2
# 实测这种方式也可以:sum=$[$a+$b]或者sum=$[a+b]
sum=$((a+b))
echo "$a+$b=$sum"

也可以使用expr表达式:

a=1
# 注意+号两边必须要有空格,否则会被成普通字符串
a=`expr $a + 2`
# 打印a的值,此时变为3
echo $a
  1. $((…))$[...]的区别是:
    前者内部数字运算可以使用>、<、=这样的符号,而后者要使用-lt(小于)、-gt(大于)、-le(小于或等于)、-ge(大于或等于)、-eq(等于)、-ne(不等于)。
  2. 内置RANDOM生成器取值范围:0-32767,示例: [ [ [RANDOM%50]

数组

  • 声明
# 枚举式
abi_list=(armeabi-v7a arm64-v8a x86 x86_64)

# 定义一个空数组
array=()		
array1[0]=a
array1[1]=b
array1[2]=c

# 字符串强转
string="12:34:56"
array=(${string/:/ })


# 使用key替代index来访问数组
declare -A user_info
user_info[name]=devops
user_info[age]=18
# 或者
user_info=([name]=devops [age]=18)

  • 访问元素
echo ${array[0]}	#获取第一个元素的值
echo ${array[-1]}	#获取最后一个元素的值

echo ${array[*]}	#所有元素作为一行输出
echo ${array[@]}    #所有元素作为多行输出

echo ${#array[*]}   #数组的长度

  • 遍历元素
# 不推荐
for((i=0; i<4; i++));do echo ${arr[i]}; done
# 推荐
for((i=0; i<${#arr[*]}; i++));do echo ${arr[i]}; done
# 推荐
for i in ${!arr[@]}; do echo ${arr[i]}; done
# 推荐
for item in "${arr[@]}"; do echo $item; done

  • 删除元素及数组
unset array2[2]			#删除索引数组的第三个元素
unset user_info[age]	#删除关联数组中索引为age的元素
unset array2			#删除数组

命令结果

# 获取当前目录赋值给变量,注意变量名可以是关键词,不过不推荐
pwd=`pwd`
# 获取当前目录的父目录并赋值给变量
my_dir=`dirname $(pwd)`

# 获取当前脚本路径,比如:./test.sh,注意这里并不是返回绝对路径
script_dir=$0

流程控制

if语句

if语法格式:

if condition;then # 必须
    ...
elif condition;then # 可选
    ...
else # 可选
    ...
fi   # 必须
condition

通常condition有三种写法:

  1. [ ... ];[[ ]]:通用,前者兼容性好,后者功能多(支持正则);
  2. (( ... )):仅数值逻辑表达式;
  3. test ...;:通用;

可以用condition快速测试脚本逻辑:[[ " c a s e 1 " =   ∗   e n c o d e r   c a p a b i l i t i e s [   ] ? ∗ case1" =~ ^*\ encoder\ capabilities[\ ]?* case1"=  encoder capabilities[ ]? ]];echo $?

也支持取反:[! ...]。下面以[ ... ]为例介绍不同变量类型的条件表达式写法:

  • 数值相关
条件写法
数字小于60if[ $var -lt 60];
if ((a<60));
if [[ a > 60]];
  • 字符串相关
条件写法
长度非0if[ -n $var]; `
长度为0if[ -z $var]; `
相同if[ $a = $b];
不相同if[ $a != $b]; `
模式匹配相同if [[ $a == $b ]]; `
正则匹配相同if [[ $a =~ $b ]]; `
  • 文件相关
条件写法
文件或目录是否存在if[ -e filename];if[ ! -e filename
目录是否存在if[ -d filename];
文件是否存在if[ -f filename];
是否有读权限if[ -r filename];
是否有写权限if[ -w filename];
是否有执行权限if[ -x filename];
是否是一个字符链接,通常与-d/-f配合使用if[ -d filename];
字符链接示例:ls -l /bin/sh
rwxrwxrwx 1 root root 4 Mar 16 2009 /bin/sh -> bash
  • 逻辑运算
条件写法
[ a = a -a b = b] && echo Y || echo N
[[ a==a && b==b ]] && echo Y || echo N
[ a = a -o b = b] && echo Y || echo N
[[ a==a || b==b ]] && echo Y || echo N
示例

示例1:数字比较

if [ $1 -gt 2 ];
then
    echo "greater then 2"
else
    echo "less or equals 2"
    exit
fi

示例2:字符串判断

# 逐行读取users.list文件
for name in $(more users.list)
do
if [ -n "$name"]; then
echo "name is '$name'"
else
echo "name is null"
fi
done

示例3:字符串模式/正则匹配

[[ 7.6 =~ 7.6.* ]]
echo $?
#输出0
[[ 7.6 == 7.6.* ]]
echo $?
#输出1
[[ apple == a* ]]
echo $?
#输出0
[[ 7.6 =~ 7.6 ]]
echo $?
#输出0
[[ 7.6 == 7.6 ]]
echo $?
#输出0

重要知识点:

  1. $?表示获取上一行命令的输出;
  2. 使用[[ a =~ b]]可以表达a.contains(b)的关系,非常实用;

case语句

语法格式:

case value in
pattern1)
    ...
    ;;
pattern2)
    ...
    ;;
*)
	...
	;;
esac
示例

示例1:字符串判断

case $1 in
start)
    echo "let's start"
    ;;
stop)
    echo "let's stop"
    ;;
esac

示例2:数字判断

read -p "Input a number: " n
a=$[n%2]
case $a in
1)
echo "odd number."
;;
0)
ehco "even number."
;;
*)
echo "not number."
;;
esac

for语句

  • 语法格式1:穷举式
for i in item1 item2 ... itemN
do
command
done

这种是比较常见的,我们已知所有值进行逐个遍历

  • 语法格式2:范围式
for ((i=1;i<=len;i++))
do 
command
done
  • 语法格式3:迭代式
for var in sequence
do 
command
done
示例

示例:打印给定范围的数字

# 打印从1到5的数字
for i in `seq 1 5`; do
	echo "$i"
done
# 打印从1到任意输入数字之间的数字
len=$1
for ((i=1;i<=len;i++))
do 
    echo $i
done

示例:遍历数组的方法

# 定义一个数组,要用空格不要用逗号!!!,用逗号会被当成一个元素
arr=(1 2 3 4)
# 方式一:索引方式遍历
len=${#aar[@]}
for ((i=0;i<len;i++))
do
	printf "%s\t%d\n" "$i" ${arr[i]}
done
# 方式二:迭代方式遍历
for item in ${aar[@]}
do
	echo $item
done 
# 方式三:索引序列迭代方式遍历
for i in "${!arr[@]}"
do 
    printf "%s-%s\n" "$i" "${arr[$i]}"  
done

在上述示例中,@都可以被*替换

示例:索引和迭代的结合

pos=0
array=( 20200630 20210731 20200831 )
for element in ${array[@]}
do
   end_date=$element
   start_date="${element: 0: 6}01"
   let pos++
   echo "序号: echo ${pos}, start_date: ${start_date}, end_date: ${end_date}"
done

示例:遍历单层目录,判断文件是否是sh脚本

#!/bin/bash
 
TESTCHARS=2
head="#!"
PATHNAME=$(pwd)
 
if [ "$#" -eq 1 -a -d "$1" ]
then
    PATHNAME=$1
fi
 
for file in $(ls $PATHNAME)
do
    if test -f $file ; then
    	# 获取文件头2个字符
        headchar=`head -c$TESTCHARS $file`
        # 判断是否是“#!”
        if test $headchar="$head"
        then
            echo "file $file is a script"
        else
            echo "file $file is not a script"
        fi
    else
        echo "file $file is not a script"
    fi
done
exit 0

while语句

语法格式:

while condition
do
    ...
done
示例
  • 示例:数字
i=6
while [$i -ge 1] do
echo $i
i=$[$a-1]
done
  • 示例:遍历数组
i=0  
while [ $i -lt ${#array[@]} ]  
do  
 echo ${array[$i]}  
 let i++  
done
  • 示例:打印九九乘法表
i=1
j=1
while (($i <= 9))
do
    while (($j<=$i))
    do
        let "k=i*j"
        echo -n "$i*$j=$k "
        let j++
    done
    let i++
    let j=1
    echo "" #换行
done

函数

自定义函数

#!/bin/bash
# 获取字符串长度
function strLen() {
    local str=$1
    local ret=0
    if [ -n "$str" ]; then
      ret=${#str}
    fi
    # 使用echo把结果返回
    echo "$ret"
}
# 函数一定要在调用之前定义,否则报错:command not found
result=$(strLen "haha")
echo "result=$result"

# 数组元素乘以3
function array() {
    local arr=($@)
    for ((i=0;i<$#;i++))
      do
        out[i]=$((${arr[i]}*3))
      done
    echo "${out[@]}"
}
nums=(1 2 3)
result=$(array ${nums[*]})
echo "result=$result"

字符串

正则表达式

参考资料:shell正则

cut

将文件按行进行剪切字节字符并将结果输出。
命令格式:cut [options] filename
选项:

  • -f:指定按列号裁剪,从1开始,需要通过-d来分割和定义列
  • -c:指定按字符裁剪
  • -b:指定按字节裁剪
    • 单列:-f n
    • 多列:-f n,m
    • 开始到n:-f -n
    • n到结束:-f n-
    • m到n:-f m-n
    • 取反:-f n --complement,除n列之外的其他列
  • -d:指定分隔符,默认是制表符(\t),仅支持单个字符

简单示例:

# 以空格为分隔符,输出每行的第1列
cut -d " " -f 1 test.txt

# 以空格为分隔符,输出每行的第2、3列
cut -d " " -f 2,3 test.txt

应用示例1:选取系统PATH变量值,第2个“:”开始后的所有路径

[bd@localServer ~]$ echo $PATH
/usr/lib64/qt-3.3/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/bd/bin
 
#### 其中的2-表示取2以及之后所有的
[bd@localServer ~]$ echo $PATH | cut -d : -f 2-
/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/bd/bin

应用示例2:切割ifconfig打印的IP地址

[bd@localServer ~]$ ifconfig eth0 | grep "inet addr" | cut -d : -f 2 | cut -d " " -f 1
192.168.1.102
awk

命令格式:awk [options] ‘pattern{action}’ filename
awk的执行过程是扫描文件的每一行,如果有和模式匹配(pattern)的,就执行后面的一系列动作(acttion)。动作的花括号‘{}’并不是一定要写的,用于和pattern进行分组。
默认情况下,回车换行默认是一行的结束,$表示第几列,举个例子:
awk '{print $3}'就是输出第三列,$0表示整行输出。

选项:

  • -F:指定字段分隔符,默认是空格
  • -v:初始化绑定变量
  • -f:从文件读取awk命令,这种适合命令较长的情况

模式:

  • 正则:
  • BEGIN : 处理开始前执行的操作,可以用来初始化变量等,只会执行一次
  • END: 处理完成后执行的操作,只会执行一次

动作:

  • print

变量:
在这里插入图片描述

除此之外,还有内置变量ARGV数组,ARGV[0]为awk,ARGV[N]下标从1开始是输入的参数

简单示例:

# 输出第一行
awk ‘NR == 1{print;}’ demo.text

# 输出最后一行
awk ‘END{print;}’ demo.text

# 输出第二列
awk{print $2}’ demo.txt

# 输出第一列和第三列
awk{print $1,$3}’ demo.txt

# 输出第二列中包含字符“zhang”的行
awk$2~/^zhang/{print;}’ demo.text

# 输出第二列不包含“yong”的行
awk$2!~/^yong/{print;}’ demo.text

# 输出第二列不包含字符“yong”并且第三列大于70的列
awk$2!~/^yong/ && $3>70{print;}’ demo.text

# 输出第二列不包含字符“yong”并且第三列大于70的列,并且处理开始前和处理完成后输出信息
awk ‘BEGIN{print “start”}$2!~/^yong/ && $3>70{print;}END{print “end”}’ demo.text

# 指定分隔符为 :,并且分别打印第一列,第二列和第三列
awk -F “:” ‘{print $1,$2,$3}’ demo1.txt

进阶用法:

# 支持一维数组、二维数组的创建
awk 'BEGIN {
sites["runoob"]="www.runoob.com";
sites["google"]="www.google.com"
delete sites["google"];
print fruits["google"]
}'

# 使用外部变量
awk -v n=1 -v m=2 'BEGIN{print n, m}'

更多awk使用示例

sed

参考文章:https://blog.csdn.net/l675655077/article/details/88808133

sed [选项] [动作] [inputfile]

  • 选项与参数:
    -n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
    -e :直接在命令列模式上进行 sed 的动作编辑;
    -f :直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作;
    -r :sed 的动作支持的是延伸型正规表示法的语法。(默认是基础正规表示法语法)
    -i :直接修改读取的文件内容,而不是输出到终端。

  • function:
    a :后插行, a 的后面可以是字串,而这些字串会在新的一行出现(目前的下一行)
    c :取代行, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行
    d :删除行,因为是删除,所以 d 后面通常不接任何参数,直接删除地址表示的行;
    i :前插行, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
    p :列印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行
    s :替换,可以直接进行替换的工作,通常这个 s 的动作可以搭配正规表示法,例如 1,20s/old/new/g 一般是替换符合条件的字符串而不是整行

一般function的前面会有一个地址的限制,例如 [地址]function,表示我们的动作要操作的行。

提示:获取行号可以用grep -n xxx

示例:

# 行号筛选
sed '1d' test.xx  # 删除第1行
sed '$d' test.xx  # 删除最后一行
sed '1,2d' test.xx  # 删除第1、2两行
sed '3,$d' test.xx  # 删除第3行及之后的所有行


# 正则筛选/regex/
sed '/2/d' test.txt  # 删除所有包含2的行
sed '/^2/d' test.txt  # 删除所有2开头的行

# 插入行
sed '1a hello world' test.txt  # 在第1行后插入一行,内容为"hello world"
sed '1a \    hello world' test.txt  # 在第1行后插入一行,内容为"    hello world"
sed '1i hello world' test.txt  # 在第1行前插入一行,内容为"hello world"

# 整行替换
sed '1c hello world' test.txt   # 替换第1行内容为"hello world"

# 行内部分替换
sed 's/aa/AA/' test.txt  # 替换行内第一个aa为AA
sed 's/aa/AA/g' test.txt # 替换行内所有aa为AA
sed '1s/aa/AA/g' test.txt  # 替换第1行行内所有aa为AA
sed '5,$s/aa/AA/g' test.txt # 替换第5行及之后所有行的行内所有aa为AA
sed '/^[0-9]/s/aa/AA/g' test.txt # 替换所有数字开头行的行内所有aa为AA

# 函数分隔符自定义
echo 'aabbccaadd' | sed s#aa#AA#g  # 其中#作用相当于/

# 仅筛选无动作
sed -n '2p' test.txt   #p表示仅输出满足筛选条件的行,加-n是防止全文输出(默认会全文输出)

# 将修改保存到源文件中
sed -i '2d' test.txt  # 命令运行之后发现test.txt的第2行没有了
  • sed正则支持
    在这里插入图片描述
expr

整数计算:

注意:数字的左右必须要有空格
expr 2 + 2
expr 2 \* 2
expr 2 / 2

i=5
i=`expr $i + 4`
echo $i

字符串提取:

#!/bin/bash
if expr "$1" : ".*\.pub"* &>/dev/null
then
   echo  " you are using $1"
else
   echo "you .gile" 
fi

计算字符的长度(Mac不支持):

expr length "$char"

日期和时间

相关命令
# 2022年 6月20日 星期一 00时05分17秒 CST
date
sleep 3s
统计脚本耗时

用 date 相减:

#!/bin/bash
startTime=`date +%Y%m%d-%H:%M:%S`
startTime_s=`date +%s`
#-------------------------------
echo "code body"
#-------------------------------
endTime=`date +%Y%m%d-%H:%M:%S`
endTime_s=`date +%s`
sumTime=$[ $endTime_s - $startTime_s ]
echo "$startTime ---> $endTime" "Total:$sumTime seconds"

用 time 工具

time sh xxx.sh
# 会返回3个时间数据
# real 该命令的总耗时, 包括user和sys及io等待, 时间片切换等待等等
# user 该命令在用户模式下的CPU耗时,也就是内核外的CPU耗时,不含IO等待这些时间
# sys  该命令在内核中的CPU耗时,不含IO,时间片切换耗时.
时间和时间戳转换

来源:Shell 时间与时间戳相换(Mac)

  • 时间戳转时间 timestamp2date.sh
#!/bin/sh

function usage(){
    echo "-h --help \n" \
         "  将10/13位时间戳转换为本地时间 \n"\
         "  参数:时间戳,支持10/13位两种 \n"\
         "  默认值:当前时间向后5min \n"\
         "  e.g. 1483430400(10位秒时间戳),1483430400000(13位毫秒时间戳) \n"
    exit 1
}

###
os_platform=`uname -s`
if [[ $# -le 0 ]]; then
    echo "默认按照当前时间向后5min取值"
    if [[ "${os_platform}" = "Darwin" ]];then
        echo `date -v+5M +"%Y-%m-%d %H:%M:%S"`
    elif [[ "${os_platform}" = "Linux" ]];then
        echo `date -d +5min +"%Y-%m-%d %H:%M:%S"`
    fi
else
    case $1 in
      -h|--help)
          usage
      ;;
      *)
          timestampStr=${1}
          length=`echo ${#timestampStr}`
          if [[ ${length} -ne 10 ]] && [[ ${length} -ne 13 ]];then
              echo "请输入10/13位数字时间戳"
              exit 1
          elif [[ ${length} -eq 13 ]];then
              timestampStr=${timestampStr:0:10}
          fi
          echo "时间戳位:${timestampStr}"
      if [[ "${os_platform}" = "Darwin" ]];then
              dateStr=`date -r${timestampStr} +"%Y-%m-%d %H:%M:%S"`
          elif [[ "${os_platform}" = "Linux" ]];then
              dateStr=`date -d @${timestampStr} +"%Y-%m-%d %H:%M:%S"`
          fi
          echo "${1}对应的本地时间为${dateStr}"
      ;;
    esac
fi

命令行执行

sh timestamp2date.sh 1507704300000
时间戳位:1507704300
1507704300000对应的本地时间为2017-10-11 14:45:00
  • 时间转时间戳 date2timestamp.sh
#!/bin/sh

function usage(){
    echo "-h --help \n" \
         "  将本地时间转换为13位时间戳(毫秒时间戳) \n"\
         "  只有1个参数:本地时间,参数格式:'%Y-%m-%d %H:%M:%S' \n"\
         "  默认值:当前时间向后5min \n"\
         "  e.g. 2017-01-01 16:00:00 \n"
    exit 1
}


##时间采用正则匹配
time_pattern="^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2} [0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}"
os_platform=`uname -s`

if [[ $# -le 0 ]]; then
    echo "默认按照当前时间向后5min取值"
    if [[ "${os_platform}" = "Darwin" ]];then
        echo `date -v+5M +%s`000
    elif [[ "${os_platform}" = "Linux" ]];then
        echo `date -d +5min +%s`000
    fi
else
    case $1 in
      -h|--help)
          usage
      ;;
      *)
          dateStr=${1}
          echo ${dateStr}
          if [[ "${dateStr}" =~ ${time_pattern} ]];then
              if [[ "${os_platform}" = "Darwin" ]];then
                  echo `date -j -f "%Y-%m-%d %H:%M:%S" "${dateStr}" +%s`000
              elif [[ "${os_platform}" = "Linux" ]];then
                  echo `date -d "${dateStr}" +%s`000
              fi
          else
              echo "时间格式不正确,请按照'%Y-%m-%d %H:%M:%S'格式输入,如'2017-01-01 16:00:00' "
          fi
      ;;
    esac
fi

命令行执行

sh date2timestamp.sh '2017-10-13 14:38:30'
2017-10-13 14:38:30
1507876710000

其他

数字序列

seq 1 5:生成1到5的数字序列

脚本调用

脚本入参

shell脚本与batch脚本一样支持调用传参,与bat等其他脚本语言类似,shell预定义了一些参数索引:

  • $0:脚本本身
  • $1:第一个参数
  • $2:第二个参数,以此类推
  • $?:上个命令的退出状态(正常退出为0),或函数的返回值
  • $$#:传参个数

下面以一个简单的脚本为例,演示脚本的调用。

有如下脚本adduser.sh,用于添加系统用户

#! /bin/bash
# Usage: ./adduser.sh username [password]

# 参数校验:如果参数数量等于0则终止脚本,返回错误码1
[ $# -eq 0 ] && echo "At least one parameter is required!" && exit 1
# 参数校验:如果用户已存在则终止脚本,返回错误码2
id $1 >& /dev/null && echo "User '$1' already exist!" && exit 2
# 创建用户,如果没有传密码则默认使用用户名作为密码
useradd $1 && if test $# -eq 1; then echo $1; else echo $2; fi | passwd --stdin $1 &> /dev/null && echo "Add user $1 success" && exit 0
# 在useradd 或者 passwd时出现了异常,很有可能是权限不足所导致
echo "Something above goes wrong." && exit 3

脚本非常干练,这里进行一下解释:

  • $#表示参数数量;
  • [ arg... ]是条件表达式,所以[ $# -eq 0 ]是判断是否传参(数量等于0表示未传参);
  • id指令查询需要创建的用户是否存在,如果用户存在,表达式id $1的返回值($?)为0;
  • >& /dev/null可以隐藏表达式id $1的输出信息(重定向到空设备),batch中也有相应的操作:pause >null
  • if test $# -eq 1; then echo $1; else echo $2; fi判断用户是否设置了第二个参数,如果没有参数二,使用用户名作为密码,这里使用了管道“|”将表达式结果流转给passwd指令;
  • useradd username passwd password添加系统用户的命令,成功则返回0;

脚本选项

现有以下脚本test.sh

#!/bin/sh
for option
do
    echo $option
done

然后我们调用该脚本:./test.sh,输出:

-a
a
-b
b

看一个更完整的示例:

for option
do 
	case $option in
	-help | --help | -h)
		want_help=yes
		;;
	-exec-prefix=* | --exec-prefix=*)
	    EPREFIX=`expr "x$option" : "x-*exec-prefix=\(.*\)"`
	    ;;
	-*)
		{ echo "error: unrecognized option: $option Try \`$0 --help' for more information." >&2
      { (exit 1); exit 1; }; 
      }
      ;; 
	esac
done

扩展阅读

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值