<Linux Shell 脚本攻略>学习笔记
Contents
shebang
在计算机科学中,Shebang(也称为 Hashbang )是一个由井号和叹号构成的字符序列 #! ,其出现在文本文件的第一行的前两个字符
bash 的配置文件执行顺序
这里自己额外想到的, 所以整理了下网上的资料
Zsh/Bash startup files loading order (.bashrc, .zshrc etc.)
+----------------|-----------|-----------|------+
| |Interactive|Interactive|Script|
| |login |non-login | |
+----------------|-----------|-----------|------+
|/etc/profile | A | | |
+----------------|-----------|-----------|------+
|/etc/bash.bashrc| | A | |
+----------------|-----------|-----------|------+
|~/.bashrc | | B | |
+----------------|-----------|-----------|------+
|~/.bash_profile | B1 | | |
+----------------|-----------|-----------|------+
|~/.bash_login | B2 | | |
+----------------|-----------|-----------|------+
|~/.profile | B3 | | |
+----------------|-----------|-----------|------+
|BASH_ENV | | | A |
+----------------|-----------|-----------|------+
| | | | |
+----------------|-----------|-----------|------+
| | | | |
+----------------|-----------|-----------|------+
|~/.bash_logout | C | | |
+----------------|-----------|-----------|------+
对于 zsh
+----------------|-----------|-----------|------+
| |Interactive|Interactive|Script|
| |login |non-login | |
+----------------|-----------|-----------|------+
|/etc/zshenv | A | A | A |
+----------------|-----------|-----------|------+
|~/.zshenv | B | B | B |
+----------------|-----------|-----------|------+
|/etc/zprofile | C | | |
+----------------|-----------|-----------|------+
|~/.zprofile | D | | |
+----------------|-----------|-----------|------+
|/etc/zshrc | E | C | |
+----------------|-----------|-----------|------+
|~/.zshrc | F | D | |
+----------------|-----------|-----------|------+
|/etc/zlogin | G | | |
+----------------|-----------|-----------|------+
|~/.zlogin | H | | |
+----------------|-----------|-----------|------+
| | | | |
+----------------|-----------|-----------|------+
| | | | |
+----------------|-----------|-----------|------+
|~/.zlogout | I | | |
+----------------|-----------|-----------|------+
|/etc/zlogout | J | | |
+----------------|-----------|-----------|------+
命令分隔
用分号或换行符. 即:
cmd1; cmd2
等同于
cmd1
cmd2
注释
#
以 #
符号及之后的行内容都是注释
终端打印
echo
- 双引号的字符串则会进行变量替换
- 单引号的字符串则不会进行变量替换
- 默认情况下, 会自动添加换行
-n
参数: 忽略结尾的换行
-e
参数: 表示包含转义序列
彩色转出
文本
颜色码:
- 0 重置
- 30 黑色
- 31 红色
- 37 白色 等等
例如
echo "\e[1;31m Hello World\e[0m"
背景
颜色码:
- 0 重置
- 40 黑色
- 41 红色
47 白色 等等
echo "\e[1;42m Hello World\e[0m"
printf
与 C 语言中的 printf
函数参数一样.
- 默认情况下, 不会自动添加换行
变量
在 Bash 中, 每一个变量的值都是字符串, 无论你给变量赋值时有没有使用引号, 值都会以字符串的形式存储.
环境变量
有一些特殊的变量会被Shell环境和操作系统用来存储一些特别的值, 这类变量就称为 环境变量
终端环境变量
使用 env
命令查看.
进行的环境变量
cat /proc/$PID/environ
上面返回的是变量的列表, 以 name=value
的形式来描述. 之间由 \0
分隔. 可将 \0
替换为 \n
就可使每一行显示了.
cat /proc/$PID/environ | tr '\0' '\n'
变量赋值
var=value
注意, 它不同于
var = value
前者才是赋值, 后者是相等操作.
引用变量的值
$var
或
${var}
注意, 如果包含在单引号里, 变量不会被扩展. 将依照原样显示.
获取变量的长度
length=${#var}
获取当前的SHELL
echo $0
或
echo $SHELL
检查是否 root 用户
if [ $UID -ne 0 ]; then
echo "Non root user"
else
echo "root user"
if
修改提示符字符串
在文件 ~/.bashrc
中的某一行的 PS1
变量中设置的.
Bash 中进行数学运算
let
使用let时, 变量名之前不需要添加 $
, 例如:
no1=4
no2=5
let result=no1+no2
echo $result
自加和自减,缩写
let no1++
let no1--
let no1+=6
let no1-=6
使用 []
result=$[ no1 + no2 ]
或
result=$[ $no1 + 5 ]
使用 (())
这种变量名之前要加上 $
result=$(( no1 + 50 ))
使用 expr
result=`expr 3 + 4`
result=$(expr $no1 + 5)
浮点运算 bc
echo "4 * 0.45" | bc
设置精度
echo "scale=2; 3/8" | bc
进制转换
echo "obase=2; 1000" | bc
obase
: 输出进制
ibase
: 输入进制
文件描述符
文件描述符是一种用于访问文件的抽象指示器. 存取文件离不开被称为
文件描述符
的特殊数字.
0 : 标准输入 stdin /dev/stdin
1 : 标准输出 stdout /dev/stdout
2 : 标准错误 stderr /dev/stderr
自定义文件描述符
文件打开模式通常有3种
- 只读
- 截断写入
追加写入
< : 从文件中读取内容到 stdin > : 用于截断模式写入 >> : 用于追加模式写入
读取
创建一个文件描述符进行读取:
exec 3<input.txt
这表示使用文件描述符3打开并读取文件.
使用文件描述符
cat <&3
注意, 只能读取一次, 若要再次读取, 则要重新使用 exec 来分配文件描述符进行二次读取.
写入
# 创建
exec 4>output.txt
# 使用
echo "ehllo" >&4
追加
exec 5>>output.txt
echo "hello" >&5
注意, 是使用 >&5
重定向
> : 旧内容会被清空, 然后设置为新的内容. 这等同于 1>
>> : 追加到旧内容尾部. 这等同于 1>>
< : 用于从文件中读取内容到 stdin
将 stderr
和 stdout
分别重定向到一个文件:
cmd 2>stderr.txt 1>stdout.txt
stderr
和 stdout
一起重定向:
cmd 2>&1 output.txt
忽略错误输出:
cmd 2>/dev/null 1>output.txt
同时重定向到文件和 stdout
command | tee FILE2 FILE2
注意, tee
只能读取 stdout
而不包括 stderr
使用 stdin
作为命令行参数:
cmd 1 | cmd2 | cmd3 -
即 -
作为命令的文件名参数即可.
多行文件重定向
cat << EOF > log.txt
这里是内容
EOF
这表示, 指示 shell 直接从当前源中读取输入, 直到有一行的内容仅包含 EOF 这个单词.
命令的成功与否
echo $?
如果返回0, 表示上一个命令执行成功, 否则是失败.
数组
起始索引为0.
定义
array_var=(1 2 3 4 5 6)
使用
echo ${array_var[0]}
打印数组所有值
echo ${array_var[*]}
或
echo ${array_var[@]}
长度
echo ${#array_var[*]}
关联数组
定义
declare -A ass_array
ass_array=([key1]=value [key2]=value2)
或
ass_array[key1]=value1
ass_array[key2]=value2
使用
echo ${ass_array[key]}
列出数组索引
echo ${! ass_array[*]}
或
echo ${! ass_array[@]}
别名
alias new_command='command sequence'
永久保存: 将内容写到 ~/.bashrc
删除别名
alias new_command=
unalias new_command
日期和时间
纪元时或 Unix 时间: UTC 1970年1月1日 0时0分0秒起所流逝的秒数.
date
打印纪元时
date +%s
从指定时间转换为纪元
date --date "Thu Nov 18 08:07:21 IST 2010" +%s
--date
用于提供日期字符串作为输入.
获取星期几
date --date "Jan 20 2001" +%A
格式化日期
date "+%d %B %Y"
设置日期和时间
date -s "格式化的日期字符串"
调试脚本
bash -x script.sh
在脚本内部, 进行部分调试
set -x
要调试的代码段
set +x
-x
: 执行时显示命令和参数
+x
: 禁止调试
-v
: 当命令进行读取时显示输入
+v
: 禁止打印输入
函数
定义
function fname() {
statements;
}
或
fname() {
statements;
}
执行
fname;
传递参数
fname arg1 arg2;
访问参数
function fname(){
echo $1, $2 # 访问参数1, 参数2
echo "$@" # 以列表的方式一次性打印所有参数
echo "$*"; # 类似 $@, 但是参数被作为单个实体
return 0; # 返回值
}
即
$1
是第一个参数$2
是第二个参数$n
是第n个参数$@
被扩展为”$1” “$2” “$3” 等$*
被扩展成 “$1c$2c$3”, 其中c是IFS的第一个字符$@
用的比$*
多, 因为$*
将所有参数当作单个字符串, 因此它很少被使用
递归
bash 支持递归调用
导出函数
函数也可以像环境变量一样用 export
导出, 如此一来, 函数的作用域就可以扩展到子进程中. 例如
export -f fname
读取命令返回值(状态)
cmd;
echo $?
$?
会给出命令 cmd 的返回值. 这个返回值被称为退出状态.
命令输出读入到变量
# 这种方法称为子shell
cmd_output=$(COMMANDS)
或
# 这种方法称为 反引用
cmd_output=`COMMANDS`
子shell
pwd;
(cd /bin ; ls);
pwd;
第二行中的语句即为子shell, 它不会对当前shell有任何影响.所有改变仅限于子shell内.
保留空格和换行
利用子shell或反引用的方法将命令的输出读入到一个变量中, 可以将它放在双引号中, 即可保留空格和换行符.
如
out=$(cat file.txt)
out1="$(cat file.txt)"
注, 像好这个没有效果. 我在 Ubuntu 16.04.2 LTS , GNU bash, version 4.3.46(1)-release (x86_64-pc-linux-gnu) 版本中测试没有效果. 有待处理.
read 命令
读取 N 个字符到变量
read -n N var_name
用无回显的方式读取密码
read -s var
显示提示信息
read -p "enter input:" var
在特定时限内读取
read -t timeout var
单位秒
用特定的定界符作为输入行的结束
read -d delim_char var
true 的命令
:
它是内建命令, 总是会返回为0的退出码.
字段分隔符 IFS
IFS 是存储定界符的环境变量, 它是当前 shell 环境使用的默认定界字符串.
它的值, 默认为 空白字符
#!/bin/bash
data="1,2,3,4,5"
oldIFS=$IFS
IFS=,
for item in $data;
do
echo Item: $item
done
IFS=$oldIFS
输出
./hello.sh
Item: 1
Item: 2
Item: 3
Item: 4
Item: 5
for 循环
for var in list;
do
commands;
done
list
可以是字符串, 也可以是一个序列.
序列
{1..50}
表示从1到50的数字列表.
{a..z}
for 的 c 风格
for ((i=0;i<10;i+)) {
commands;
}
while 循环
while condition
do
commands;
done
until 循环
until condition;
do
commands;
done
if
if condition;
then
commands;
fi
if .. elif .. else
if condition;
then
commands;
elif condition; then
commands;
else
commands;
fi
算术比较
[ $var -eq 0 ]
注意, 中括号与操作数之间要有空白.
- -gt : 大于
- -lt : 小于
- -ge : 大于等于
- -le : 小于等于
组合
[ $var -ne 0 -a $var2 -gt 2 ] # 表示逻辑与, -a
[ $var -ne 0 -o $var2 -gt 2 ] # 表示逻辑或, -o
文件系统测试
[ -f $file_var ] # 如果是正常的文件路或文件名
[ -x $var ] # 如果给定的变量包含的文件可执行, 则返回真
[ -d $var ] # 如果是目录
[ -e $var ] # 如果文件存在
[ -c $var ] # 如果是字符设备
[ -b $var ] # 如果是块设备
[ -w $var ] # 如果文件可写
[ -r $var ] # 如果文件可读
[ -L $var ] # 如果是符号链接
字符串比较
最好使用双中括号
[[ $str1 = $str2 ]] # 如果相等
[[ $str1 == $str2 ]] # 如果相等
[[ $str1 != $str2 ]] # 如果不相等
[[ $str > $str2 ]] # 大于
[[ $str < $str2 ]] # 小于
[[ -z $str1 ]] # 如果是空字符串
[[ -n $str1 ]] # 如果是非空字符串
组合
[[ -n $str1 ]] && [[ -z $str2 ]]
test 命令
if [ $var -eq 0 ]; then
echo "True";
fi
与可以写为
if test $var -eq 0; then
echo "True";
fi
cat
- -s : 压缩多行空行为一行空行
- -T : 显示制表符
- -n : 显示行号
- -b : 显示行号, 但不为空白行设置行号
find 命令
find base_path -print0
- -print0 表示以 ‘\0’ 作为文件名的界定符. 默认为换行符, 但当文件名包含换行符时, 这就有用了. 注意, 是连起来的
-print0
- -name “*.txt” 正则匹配.
- -iname “*.txt” 忽略大小写
OR
find . \( -name "*.txt" -o -name "*.pdf" \) -print
否定
find . ! -name "*.txt" -print
表示匹配所有不以 *.txt
结尾的文件名.
基于目录深度
默认情况下, find 会遍历所有子目录.可以添加选项来限定深度.
- -maxdepth
- -mindepth
这两个参数应该是 find 的第三个参数, 否则可能会影响效率.
根据文件类型搜索
- -type d 目录类型
- -type f 文件类型
- -type l 符号链接类型
- -type c 字符设备
- -type b 块设备
- -type s 套接字
文件时间
- -atime : 用户最近一次访问时间
- -mtime : 文件内容最后一次修改时间
- -ctime : 文件元数据最后一次改变的时间
后接 +N 或 -N
单位是天. 负号表示是小于, 正号表示大于
- -amin : 访问时间
- -mmin : 修改时间
- -cmin : 变化时间
这些同上, 只是时间单位为分.
文件大小
- -size +2K : 表示大于 2KB 的文件
- -size -2K : 表示小于 2KB 的文件
单位
- b : 512 字节
- c : 字节
- w : 2字
- k : 1024 字节
- M : 1024 K 字节
- G : 1024 M 字节
删除匹配
- -delete
基于权限
- -perm 664
动作
-exec
find . -type f -exec cat {} \;> outout.txt
多命令
exec 不支持执行多命令, 但可以变换一个, 将命令写入脚本, 然后调用
-exec ./commands.sh {} \;
tr 命令
转换源.
映射
tr [options] set1 set2
删除字符
tr -d '[set1]'
压缩字符
tr -s ' '
将多个空白压缩为一个.
校验和
md5sum filename
sh1sum filename
加解密
crypt
它是一个简单的加密工具, 从 stdin 接受一个文件以及口令作为输入, 然后将加密数据输出到 stdout
crypt < input_file > output_file
解密
crypt -d < encrypted_file > output_file
gpg
加密
gpg -c filename
解密
gpg filename.gpg
base64
base64 filename > outputfile
解码
base64 -d file > outputfile
临时文件
filename=`mktemp`
dirname=`mktemp -d`
仅生成文件名, 而不是实际的文件
tempfile=`mktemp -u`
根据模板创建
mktemp test.XXX
它会生成 test.XXX
形式的临时文件.
提取文件名和扩展名
file_jpg="sample.jpg"
fileName=${file_jpg%.*}
suffixName=${file_jpg#*.}
注意, 单个 %
是非贪婪操作, 想要贪婪式的, 则使用 %%
类似的, 还有 #
是非贪婪的, 而 ##
是贪婪的.
并行操作
在循环里的命令, 使用 &
时, 它就会直接向下再执行了, 而不会等待命令执行完成.这样子就可以利用多核
#!/bin/bash
PIDARRAY=()
for file in File1.iso File2.iso
do
md5sum $file &
PIDARRAY+=("$!")
done
wait ${PIDARRAY[@]}
注意, 还要用 wait 来等待多进程的退出.
dd 命令
dd if=/dev/zero of=data.file bs=1M count1
不可删除的文件
chattr +i file
删除的时候, 它会提示
rm: remove write-protected regular empty file 'file'?
恢复为可删除
chattr -i file
diff 与文件补丁 patch
diff -u version1.txt version2.txt > version.patch
patch 命令可将修改应用于任意文件. 当应用于 version1.txt
时, 就可以得到 version2.txt
, 当应用于 version2.txt
时, 就可以得到 version1.txt
patch -p1 version1.txt < version.patch
取消修改
patch -p1 version1.txt < version.patch
递归的形式来处理目录及文件
diff -Naur dir2 dir2
快速定位目录
pushd 压入
popd 弹出
dirs 查看栈的内容
cd - 返回上一次的目录
它是一个处理LIFO的数据结构. 目录被存储在栈中, 然后 push 和 pop 进行切换.
wc 命令
wc -l file # 统计文件 file 的行数
wc -w file # 单词数
wc -c file # 字符数
wc file # 行数, 单词数, 字符数
wc file -L # 最长一行的长度
正则
| 正则 | 描述 |
|--------|---------------------------------|
| ^ | 行起始标记 |
| $ | 行结尾标记 |
| . | 任意一个字符 |
| [] | 在 [] 中的任意一个字符 |
| [^] | 在 [^] 之外的任意一个字符 |
| [-] | 在 [-] 指定范围内的任意一个字符 |
| ? | 匹配之前的项1次或0次 |
| + | 匹配之前的项1次或多次 |
| * | 匹配之前的项0次或多次 |
| () | 创建一个用于匹配的子串 |
| {n} | 匹配之前的项n次 |
| {n, } | 匹配之前项至少n次 |
| {n, m} | 匹配之前项n到m次 |
| \ | 转义 |
| 一竖 | 匹配两边任意一项 |
cut 命令
按列切分
提取指定列
cut -f 2,3 filename
这列表只显示第2,3列
排除指定列
cut -f3 --complement filename
这表示除第3列之外的所有列.
指定界定符
cut -d ";" filename
指定字段的字符或字节范围
cut -c1-5 filename
表示只打印前1~5个字符.
sed 命令
stream editor
替换字符串
sed 's/pattern/replace_string/' file
应用到文件
sed -i 's/pattern/replace_string/' file
全局应用
默认情况下, sed 将在每一行的第一处符合的内容替换. 要全部则:
sed 's/pattern/replace_string/g' file
只在第N处开始替换
sed 's/pattern/replace_string/2g' file
界定符
除了 /
还可以:
sed 's:text:replace:g'
sed 's|text|replace|g'
移除空白行
sed '/^$/d' file
awk
awk '{BEGIN {print "start"} pattern {commands} END {print "end"}}' file
处理流程:
- 执行 BEGIN {commands} 语句块中的语句
- 从文件或stdin 中读取一行, 然后执行 pattern {commands} , 重复这个过程, 直接文件全部被读取完.
- 当读至输入流末尾时, 执行 END {commands} 语句块.
特殊变量
- NR : 当前行号
- NF : 当前行的字段数
- $0 : 当前行的内容
- $1 : 第一个字段的内容
- $2 : 第二个字段的内容
将外部变量传递给 awk
awk -v VAR=$OUT_VAR '{print VAR}'
设置字段界定符
awk -F: '{print $1}'
这样子, 就将字符界定符设置为 :
了.
内建函数
length(string) : 返回字符串的长度
paste 命令:列拼接
cat 是按行拼接的.
paste 则是按列来拼接的.
tac
逆序显示. cat 的逆单词.
wget
命令行下载工具
指定多个下载
wget URL1 URL2
指定输出文件
wget URL1 -O outputfile -o logfile
重试次数
wget -t 5 URL1
为0时, 表示无限重试.
限速
wget --limit-rate 20k URL
最大下载配额
wget -Q 100m URL
表示最大下载 100m 大小, 超过时就会停止.
断点续传
wget -c URL
复制整个网站
wget --mirror --covert-links URL
curl 命令
避免显示进度
curl --silent URL
下载
curl URL --silent -O
#
型进度条
curl URL --progress
断点续传
curl -C - URL
-C -
表示让 curl 自动计算.
设置 referer
curl --referer referer_url URL
设置 cookie
curl --cookie "k=v;k1=v1"
将 cookie 另存为文件
curl URL --cookie-jar cookie_file
或简写
curl URL -c cookie_file
请求时, 带上cookie文件中的cookie
curl URL -b cookie_file
限制带宽
curl URL --limit-rate 20k
只打印头部
curl -I URL
tar 命令
打包并压缩
tar -cvjf file.tar.bz2 file1 fiel2
tar -cvzf file.tar.gz file1 file2
解压
tar -xvjf file.tar.bz2
tar -xvzf file.tar.gz
rsync 命令
rsync -avzh source_path dest_path
rsync -avzh -e "ssh -p 22" remote_path dest_path
排除
rsync --exclude "*.txt"
同步时在dest_path中删除源端不存在的文件
rsync --delete
fsarchiver 命令
制作全盘镜像
创建
fsarchiver savefs backup.fsa /dev/sda1
多个分区同时备份
fsarchiver savefs backup.fsa /dev/sda1 /dev/sda2
恢复
fsarchiver restfs backup.fsa id=0,dest=/dev/sda1 id=1,dest=/dev/sdb2
脚本中使用 EOF 的 raw string
IFS='' read -r -d '' String <<"EOF"
<?xml version="1.0" encoding='UTF-8'?>
<painting>
<img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
<caption>This is Raphael's "Foligno" Madonna, painted in
<date>1511</date>-<date>1512</date>.</caption>
</painting>
EOF
如果又想在 EOF 中引用变量, 则可以: (即去掉第一个 EOF 的双引号)
IFS='' read -r -d '' String <<EOF
<?xml version="1.0" encoding='UTF-8'?>
<painting>
<img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
<caption>This is Raphael's "Foligno" Madonna, painted in
<date>1511</date>-<date>1512</date>.</caption>
</painting>
EOF
遇到控制字符
illegal character: ^M
tr -d $'\r'
注意那个 $