shell 声名
解析器声名,#!/bin/sh
是指此脚本使用/bin/sh
来解释执行,#!
是特殊的表示符,其后面根的是此解释此脚本的 shell 解释器的路径。
#!/bin/sh
# 默认:执行脚本的时候,如果遇到不存在的变量,默认忽略它,继续向下执行。
# 设置执行脚本如果遇到不存在的变量,则报错
set -u
# 设执行输出结果前,打印执行的是哪一条指令
set -x
shell 解释器有很多,常用的是 Bash(解释器#!/bin/bash
) 和 Dash(解释器#!/bin/sh
),Linux 操作系统缺省的 shell 是 Bash,但 Bash 过于复杂,便基于 Bash 精简出符合 POSIX 标准的 Dash,二者大体相同,但标记为#!/bin/sh
的脚本不应使用任何 POSIX 没有规定的特性(如 let 等命令, 只有#!/bin/bash
可以)。
不同系统下书写的 shell 脚本可能有有细微差距,例如在 Windows 下写的的 shell 脚本,可能会由于空格不一致的原因,在 Mac 环境运行报错。解决办法,转换格式即可,假设 shell-test.sh 脚本是 Windows 下生成的脚本,我们使用 vim 转换为 Mac 下可运行的脚本。
vi shell-test.sh
# 显示 dos
:set ff
# 转换为unix格式
:set ff=unix
# 退出并保存
:wq
shell 变量
shell 变量的声名
- 只能使用数字,字母和下划线,且不能以数字开头;
- 变量名区分大小写;
- 声名变量”=“前后不能有空格(初学者很容易踩此坑)。
#!/bin/sh
echo "测试变量声名"
# 声名字符串变量 hello world!
myVar="hello world!"
# 声名数字变量 88
muNum=88
# 打印结果 hello world!
echo $myVar
echo $muNum
$ 关键字的使用
$ 关键字有很多用途,例如cd "$(dirname "$0")"
命令可将执行环境切换到当前 shell 文件所在目录。
$0
当前脚本的文件名;$n
传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1
,第二个参数是$2
;$#
传递给脚本或函数的参数个数;$*
和$@
传递给脚本或函数的所有参数;$?
上个命令的退出状态,或函数的返回值;$!
当前 shell 最后运行的后台 PID;$$
获取当前shell的进程号(PID)$()
与``(反引号)都是用来做命令替换的;${var}
返回变量值;${#var}
计算 shell 字符串变量的长度;$(())
是用来作整数运算的。
我们使用 shell-test.sh 脚本进行测试。
#!/bin/sh
# 定义一个变量 my_var
my_var="abcdefg"
# 自定义方法 MyFunc
MyFunc() {
# 输出当前脚本的文件名 /Users/lifei/Desktop/shell-test.sh
echo "$0"
# 输出函数的第 1 个参数 qwertyuiop
echo "$1"
# 输出函数的第 2 个参数 1234
echo "$2"
# 输出函数的第 3 个参数 7890
echo "$3"
# 输出传递给脚本或函数的参数个数 3
echo "$#"
# 输出传递给脚本或函数的所有参数 qwertyuiop 1234 7890
echo "$*"
# 输出传递给脚本或函数的所有参数 qwertyuiop 1234 7890
echo "$@"
# 输出上个命令的退出状态,或函数的返回值,输出 0 表示上个命令执行成功
echo "$?"
# 输出当前 shell 最后运行的后台 PID,可能为空
echo "$!"
# 输出获取当前shell的进程号(PID),不固定,例如 29488
echo "$$"
# 输出命令 1234 + 1 的执行结果 1235
echo "$(expr $2 + 1)"
# 输出命令 1234 + 1 的执行结果 1235
echo "`expr $2 + 1`"
# 输出 echo mmmmmm 命令的执行结果 mmmmmm
echo "`echo mmmmmm`"
# 输出 my_var 变量的值的长度 7
echo "${#my_var}"
# 输出 88 + 11 的结果 99
echo "$((88 + 11))"
}
# 切换到当前目录下,这样讲脚本拖入终端即可运行
cd "$(dirname "$0")" || exit 0
# 执行函数 myFunc,并传入 3 个参数
MyFunc "qwertyuiop" 1234 7890
字符串截取
使用 # 和 % 可快速的匹配截取字符串,其中 # 去掉左边字符,% 是去掉右边字符,单一符号是最小匹配,两个符号是最大匹配。假设我们定义了一个变量为:
file=/dir1/dir2/dir3/my.file.txt
可以用${ }分别替换得到不同的值:
${file#*/}
:删掉第一个 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt${file##*/}
:删掉最后一个 / 及其左边的字符串:my.file.txt${file#*.}
:删掉第一个 . 及其左边的字符串:file.txt${file##*.}
:删掉最后一个 . 及其左边的字符串:txt${file%/*}
:删掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3${file%%/*}
:删掉第一个 / 及其右边的字符串:(空值)${file%.*}
:删掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file${file%%.*}
:删掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my${file:0:5}
:提取最左边的5个字节:/dir1${file:5:5}
:提取第5个字节右边的连续5个字节:/dir2${file/dir/path}
:将第一个dir替换为path:/path1/dir2/dir3/my.file.txt${file//dir/path}
:将全部dir替换为path:/path1/path2/path3/my.file.txt
sed 的使用
sed 可以说是最有用的 shell 命令之一了,但在 Mac 上使用 sed 命令还是有一些坑需要注意的。
Mac 强制要求备份
例如有一个 test.txt 的文本文件,我们需要将文本里面所有的 abcd 更换为 xyzw,当我们执行 sed 编辑命令:
# 将文本里面所有的 abcd 更换为 xyzw
sed -i 's/abcd/xyzw/g' test.txt
报错:sed: 1: “test.txt”: undefined label ‘est.txt’。原因是 Mac 强制要求备份,否则报错,解决方案加上一个备份文件名即可。
# 备份文件名任意即可,也可为空,以下写法都可以
sed -i '' 's/abcd/xyzw/g' test.txt
sed -i '.bak' 's/abcd/xyzw/g' test.txt
转义字符 \
例如在 test.txt 的文本文件中搜索以 abcd 开头的行,并在前面加上双斜杠//,当我们执行 sed 编辑命令:
# 搜索所有以 abcd 开头的行,并在前面加上双斜杠//
sed -i "" "s/^abcd///&/g" test.txt
报错:sed: 1: “s/^abcd///&/g”: bad flag in substitute command: ‘/’。原因是 / 发生了转义,我们需要使用 \/ 来进行转义。
# ^abcd 表示以 abcd开头的行,\/ 表示 /,& 表示匹配到字符串,g 表示替换全部
sed -i "" "s/^abcd/\/\/&/g" test.txt
sed 基本使用
sed 模式选择的区别,经常使用的选项是sed -n
和sed -i
,其他的作为了解。
- -n∶使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN的资料一般都会被列出到萤幕上。但如果加上 -n 参数后,则只有经过 sed 特殊处理的那一行(或者动作)才会被列出来;
- -e∶直接在指令列模式上进行 sed 的动作编辑;
- -f∶直接将 sed 的动作写在一个档案内, -f filename 则可以执行 filename 内的sed 动作;
- -r∶sed 的动作支援的是延伸型正规表示法的语法(预设是基础正规表示法语法);
- -i∶直接修改读取的档案内容,而不是由萤幕输出。
还有常用命令,例如删除命令sed '1d' test
表示删除第一行。
- a∶新增,a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行);
- c∶取代,c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行;
- d∶删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
- i∶插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
- p∶列印,亦即将某个选择的资料印出。通常 p 会与参数 sed -n 一起运作;
- s∶取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法,例如 1,20s/old/new/g。
使用 test.txt 的文本文件举例。
# 读取 test.txt 文本,并删除第一行,输出剩余的行
sed '1d' test.txt
# 直接编辑 test.txt 文本,并删除第一行
sed -i '' '1d' test.txt
# 读取 test.txt 文本,删除最后一行,输出剩余的行
sed '$d' test.txt
# 读取 test.txt 文本,删除第一行到第五行,输出剩余的行
sed '1,5d' test.txt
# 读取 test.txt 文本,删除第二行到最后一行,输出剩余的行
sed '2,$d' test.txt
# 显示第一行
sed -n '1p' test.txt
# 显示最后一行
sed -n '$p' test.txt
# 显示第一行到第二行
sed -n '1,2p' test.txt
# 显示第二行到最后一行
sed -n '2,$p' test.txt
# 查询并输出包括关键字 abcd 所有所在行
sed -n '/abcd/p' test.txt
# 查询包括关键字 \ 所在所有行,使用反斜线 \ 屏蔽特殊含义
sed -n '/\\/p' test.txt
# 在 1 到 10 行搜索所有以 abcd 开头的行,并在前面加上双斜杠//
sed -i "" "1,10s/^abcd/\/\/&/g" test.txt
其他
shell 使用过程中可能会遇到各种各样的情况,例如需要拷贝至剪贴板等需求,这里只是总结一些常用操作,如下 shell 使用示例。
假设有这样一个需求,逐行读取文件 test.txt 里面的文本,判断是否是空行,如果不是空行,就保存到另外一个文件里面 dst.txt 里面,并在文本前面加上来源行号,最后将 dst.txt 文本文件的所有行拷贝至剪贴板。
#!/bin/sh
# 切换运行环境至当前 shell 文件所在目录
cd "$(dirname "$0")" || exit 0
# 定义当前读取的行为1
row=1
# 逐行读取当前目录下的test.txt
while read -r line
do
row=$((row + 1))
# 判断当前行是否为空行,空行继续
if [ "${line}x" = "x" ]; then
continue
fi
# 保存有字符的行到另外一个文件
echo "${row}:${line}">>dst.txt
done < test.txt
# 将新文本第一行至最后一行拷贝至剪贴板
sed -n '1,$p' dst.txt | pbcopy
如果您觉得有所帮助,请在GitHub Shell上赏个Star ⭐️,您的鼓励是我前进的动力。