Skip to content

前言

开发中总是遇到各种脚本,目前只能对普调的脚本进行阅读和编写,系统性的记录一篇开发中所需要的脚本技能,同时将平常遇到的脚本难点作为讲解。脚本分为shell脚本和bat脚本

shell

脚本处理程序

如果对脚本程序需要直接执行,在脚本前面添加#!/usr/bin/env sh使用默认执行程序,/usr/bin/env 会自动查找环境的sh的程序来执行

对于需要全局使用的脚本,有两种方式,第一个是软连接或者放到/usr/local/bin/ 中,第二个是使用别名 alias some="/path/xxx"

输入输出

read 能从当前stdin获取输入并存储到某个变量中 read -p "请输入值" -n 10 -s -t 20 someValue # -n 限制文本长度 -s 密码输入,不会显示, -e 启用命令补全 -t 输入时长 echo "$someValue"

echo ""转义输出echo ''不转义输出 echo ``执行转义后的结果并输出

printf 是高级的输出工具,具有序列化的功能。printf "string" ...args printf "%s-5 %c $-4.2f" 郭靖 男 66.1234

shell执行时总是默认开启了三个文件描述符为0 1 2 用于输入,输出和stderr。在shell中执行的命令默认也是使用这三个文件。通过 command > file; command >> file 将 stdout指向另一个文件。 使用 2 &> 1(&> file) 将stderr 指向 stdout ,使用 command < file 将stdin 指向file

命令执行

command1;command2; 命令都是按照;结尾(换行符前面的可以省),命令都是从前往后执行,即使前面的命令错误 command1 && command2 || command3 如果命令command1 成功则执行command2,失败则执行command3。成功错误失败根据进程返回0 1 2来判断

变量

只有字符串,没有数字布尔等类型,变量默认只在当前shell或者脚本(新开了一个子shell来执行脚本)中有效。变量通过$name 或者 ${name}来获取

截取字符串

${day_id:0:7} 0 起始位置 7 截取多少个

sh
text="wo shi yi da shuai bi"
# 截取
echo "who are you: ${text:0:2}"  # wo
# 替换
echo "who are you: ${text/shuai bi/sha bi}"  # wo shi yi ge da sha bi
# 删除留剩下的
echo "who are you": ${text%%shuai*}

if控制

sh
if command; then
    command;
elif command; then
    command
else 
    command
fi

控制语句如上,command表示退出码,除了0是成功其余都是失败,如果要测试表达式的退出码,使用[expr] 来判断

case控制

sh
case word in 
glob)
    command;; break,不在执行后面的语句
    command;&  执行当前语句并在接着进入下一个语句(不匹配直接执行)
    command;;& 继续下一个语句的匹配
sh
cat << TIP
你最喜欢哪个国家
1. 中国
2. 日本
3. 美国
TIP
read choose
case $choose in
    1) 
        echo "选择了中国,真棒";
        lang=ch
    2) 
        echo "选择了日本,给你一棒子";
        lang=jap;;
    3) 
        echo "选择了美国,小垃圾";
        lang=a
esac
printf "%s %s" choose lang

while 循环

sh
while commmand; do
    command;
done;

for 循环

sh
for i in "1 2 3 4 5 6" do  # 循环带空格的字符串,根据IFS变量定义的内容来分割

done

for file in ./*  # 遍历文件名
done

for item in (1 2 3 4 5); do # 遍历数组
echo $item;
done

for i in {1..10} do  # 遍历1到10

done

for i in seq 1 10 do # 循环 10次

done

name="123
123
123"

数组

some=(v1, v2, v3)

算数表达式

shell没有内置表达式,只有单纯的变量声明,一切基于指令,部分指令提供了简写的形式。表示式都是被指令支持的。不能直接sum=1+1 比如 let sum=1+1, 理解成let指令,传入了一个sum=1+1 参数。let指令可以简写成((sum=1+1))

函数

sh
add() {
    let sum=1+1
    echo $sum # 将sum的值作为函数返回值
    return
}

shell的函数没有返回值,只是单纯的调用。函数内部有一些特殊的变量$0(脚本名),$N(参数1~N),$#(参数个数), $@ 全部参数

sh
add() {
    echo "当前参数个数: $#"
    echo "当前函数名称: $FUNCNAME"
    echo "分别是\n"
    local i=1
    for ((i=;i <= $#;i++))
    do
        echo "${i}"
    done
}
add 1 3 2

特殊符号

  • $取变量值
  • ""转义字符串
  • ''不转义
  • ()在子shell中执行()中的代码
  • $() 将()中结果stdout,和反引号包裹相同,如果作为命令执行,是将结果执行,如果作为赋值,将结果赋值
  • {} 在当前shell自行命令
  • [] 变量的测试,或者用于计算表单
  • (())计算算数表达式, 或者在for中((i=0;i<3;i+++))

关于()和{}区别

sh
name=test
(name=test1)
echo $name  # test
{name=test1} # 
echo $name  # test1

算数运算

expr 指令用于算数的计算,计算符号之前需要有空格,大致逻辑是expr 通过空格来区分不同的参数。['expr', $a, "+", $b]这种逻辑,expr运算后会返回结果

((expr 1 + 1)) (expr 1 + 1)

条件表达式需要放在[]中, [] 也被视为一个命令 [ ! $? -eq 0 ] && xxxx 表示上一个命令的成功执行后续的,为了在兼容性推荐使用[[ ]] 语法,可以实现 [[ command1 && command2 ]] 操作

还有关系运算符 ! -eq -ne -gt -lt -ge -le 都只能在[],注意中括号内两边都有空格

提供了文件系统检查功能 [ -e file ] 文件是否存在 [ -d file ] 是否是目录

从上面可以看出[ ] 也是一个命令,返回 0 1

正则表达式

shell中部分命令支持正则表达式,主要是grep,awk, sed和cut。shell中正则表达式分为基本正则和扩展正则,不同的命令实现不一致。

基本正则表达式
. * $ \ ^ \{n\}  还有单词编辑等

常用于grep命令中 echo "1 2 33 4 aaa" | grep -n ".{1}3"

扩展正则表达式

不需要讲{}转义括起来,支持+?等量词,同时支持分组和逻辑or,用于sed -r grep -E awk中

IFS

常见命名都通过该项进行分割,比如for in, cut, awk等命令

shell三剑客之grep

命令格式如下 grep [option] pattern [...files] 选项有如下

  • -i 忽略大小写
  • -v 反向匹配
  • -o 只显示匹配命中的字符串(默认是显示整行,高亮命中的字符串)
  • -c 统计命中的行数,常用于判断是否匹配中。
  • -E 使用扩展正则表达式
  • -R 递归的匹配目录下的文件
  • -n 显示行号

shell三剑客之sed

sed是一个流式的替换查找的命令,使用基本正则表达式格式如下 sed [options] 'start,end /mode/pattern/mode-args/' [...file]

替换文件中的unix文本为linux。 echo "this is a unix operator" | sed 's/unix/linux/'

选项中有-i 直接在源文件上进行编辑,-n 仅显示处理后的结果(否则会展示整个文件内容) -e 设置多个处理命令

行定位
  • 1,4 第一行到第四行
  • 1,+3 第一行到+3行
  • 4,$ 4行到尾行
  • /test/,/linux/,出现第一次test的行到出现/linux/的行
  • 3,5!除了3~5行的意外匹配
sed查找内容 p、=和n

n表示跳过当前这行, ;是执行多个命令,p;n表示读入当前这行,然后立刻跳过下一行,所以是技术行执行 p表示打印输出 p会输出整行,如果只需要显示匹配的行号使用 =

sed查找并替换c 和 s

s 替换字符串,c替换整行 sed 's/linux/unix/g' // 多次替换 sed 's/linux/unix/' // 一行只替换一次 sed 's/linux/unix/2' // 替换第二个匹配的的 sed 's/^/#/' // 行首添加# sed 's/[0-9]*/1&' // 所有数字前面添加1

sed 删除某些行

sed '3d' 删除第三行 sed '2,+1d' 删除2+1行 sed '$ d' 删除末尾那行
sed '/^$/ d' 删除所有空行

H 和 a

H复制部分到剪贴板,a插入到某行后面 sed -i "1,3H;9a/adfs/" 复制1,3行到剪贴板并且在9行后面插入adfs

shell三剑客之awk

awk使用扩展正则表达式,先按照行处理,再将行进行分割。命令格式如下,务必记住是单引号

awk [option] 'mode {action}' ...files
  • awk -F ":" '/^root/' < /etc/passwd 查找root开头的行
  • awk -F ":" 'NF==2{print NF,$0}' 打印第一行,并添加行号
  • awk -F ""

bat脚本

bat脚本是运行在dos上面的脚本, 大致也是通过一条一条的指令来实现

变量

通过set指令 set a=123 获取变量通过%a%,通过 set /p 获取用户输入, 通过 set /a 设置一个数字

bat
@REM
set a=123
echo var is %a%

控制语句

if [not] %a%==1 (command) else command

括号中的语句可以多行执行

bat
@echo off
echo 请输入数字,如果文字大于10,显示true,反之显示false
echo.
set /p input=
if not %input% GTR 10 (
    echo 小于等10
) else (
    echo 大于10
)

运算符

算术运算 + - * / %%(取余),在表达式中会开启运算(set /a,if) 逻辑运算 equ neq lss leq gtr geq 赋值运算 += -= *= /= %=

函数

通过call :functionName调用,函数末尾通过got:eof标记结束。 对比一下shell和bat脚本的函数

sh
sum() {
    sum_a=$1
    sum_b=$2
    echo $(($1+$2))
}
result=$(sum 3 5)
echo $result # echo 8
bat
@echo off

:: 不需要添加%result%。自动处理
call:sum 4 5 result
echo %result%

:: 函数需要放在最后或者单独定义一个函数区来跳过
goto func_end
:func_start
:sum
echo %1
echo %2
:: 通过修改传入的参数来返回函数值
set /a %~3=%1+%2 
echo %~3
goto:eof
:func_end

bat的函数功能严格意义上应该是call的调用另一个bat脚本,在当前cmd中执行, 而且还会等到call的脚本执行完毕才会回到主进程 除了call, 还有start脚本,是另外开一个窗口执行bat脚本。

输入输出

输入输出和shell上一致,都有重定向输出>,追加>>和错误输出2>err.txt,同样也是 dir>nul输出到垃圾箱。 2 &>1 2和1相同输出。

多命令执行

bat通过一行一个指令的执行,&&、||相同 ,还有一个 & 和 shell的;相同,同一行执行多条命令。除此之外还有()可以扩展成多行

bat 技巧

扩展参数

将参数扩展为完整的路径,相对路径补全成绝对路径

bat
@echo off
:: 当前脚本第一个参数
echo %~1
:: 当前脚本第一个参数,并且补全到不带驱动器的圈路径,到目录
echo %~p1
:: 当前脚本第一个参数,并且补全到带驱动器的全路径
echo %~dp1
:: 脚本本身的补全
echo %~dp0
@REM 全路径补全,包含文件本身
echo %~f0
pause