简介
参考:
Shell Script 是一个在 Unix Shell 上运行的程序(A shell script is a computer program designed to be run by the Unix shell, a command-line interpreter),而 Unix Shell 是 Unix 系统中的命令行借口工具(感觉就是我们常见的 Terminal)。编写 Shell Script 也有一套自己的语法。
Linux 的 Shell 种类众多,常见的有:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Shell for Root(/sbin/sh)
这其中,前两者 /bin/sh
和 /bin/bash
非常类似,bash 可以算是 sh 的加强版,早就已经被各大系统 Unix-like 系统所支持,例如常见的 Ubuntu 和 Mac OS。当然两者在运行时稍有区别,可以参看上面的参考链接。总的来说,bash 功能更强大,推荐使用 bash。
bash 使用方法和简单例子
一个 bash shell script 文件通常是以 .sh
后缀结尾,不过其实文件格式并不影响脚本执行,只是为了区分和其他程序的区别。
一个简单的 bash script 的例子 hello_world.sh
:
#!/bin/bash
echo "Hello World !"
其中,第一行必须是 #!/bin/bash
,表示这是一个 bash 文件(如果是 sh 的话则是 #!/bin/sh
)。
运行该文件的方法很简单,前往该文件路径下,然后在终端运行:
chmod +x ./hello_world.sh # 给定权限
./hello_world.sh # 运行该脚本
注意,通常每个新的脚本文件都要事先给定权限才能运行。
判断系统类型
How to check if running in Cygwin, Mac or Linux?
上面链接介绍的很详细了。有多种方法,其中一种是这样:
#!/usr/bin/env bash
if [ "$(uname)" == "Darwin" ]; then
# Do something under Mac OS X platform
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
# Do something under GNU/Linux platform
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
# Do something under 32 bits Windows NT platform
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
# Do something under 64 bits Windows NT platform
fi
for 循环的使用
参考:
- https://stackoverflow.com/questions/8789729/how-to-zero-pad-a-sequence-of-integers-in-bash-so-that-all-have-the-same-width
- https://stackoverflow.com/questions/169511/how-do-i-iterate-over-a-range-of-numbers-defined-by-variables-in-bash
下面例子包括 for 循环的使用,以及在数字前面加上 leading 0s 的技巧:
# 最普通的遍历形式,从 0 到 10
# 注:如果要把多行命令放在一行,需要在每行命令结尾使用分号
for i in {0..10}; do echo $i; done
# 使用 seq(start, interval, end) 可以灵活选择开始位置、间隔和停止位置,并且可以使用变量。
# 这里的 `expr XXX`目的是在 bash 中增加计算式子。
N=10
for i in $(seq 0 1 `expr $N - 1`)
do
touch $i.txt # 生成一些空文件
done
# 在每个数字前面加上一定位数的 0(0 padding in front),不过必须结合 printf 命令使用。
# 里面的 %3d 中的 3 是数字总位数,例如 001
for i in $(seq 0 1 `expr $N - 1`)
do
mv $i.txt `printf frame%03d.txt $i`
done
# 下面这种方法也可以在数字前 pad 0。输出是:00010, 00011, ..., 00015
for i in $(seq -f "%05g" 10 15)
do
echo $i
done
获取 basename, dirname, extension 的技巧
audio_path=/Users/my_name/audio_test.wav
# 输出是 audio_test.wav
filename=$(basename ${audio_path})
# 输出是 audio_test
filename=$(basename ${audio_path} ".wav")
# 输出是 /Users/my_name/audio_test,即:只去掉了.wav这个扩展名。下面形式还可以
# 扩展到更灵活的形式。如接下来的一个例子
filename=${audio_path%%.*}
# 输出是 /Users/my_name/audio_
filename=${audio_path%%test*}
# 输出是 /Users/my_name
filename=$(dirname ${audio_path})
定义变量并运算
Ref:
- https://linuxhint.com/bash_arithmetic_operations/#:~:text=’let’%20is%20another%20built%2D,of%20the%20’expr’%20command.
- https://www.shell-tips.com/bash/math-arithmetic-calculation/#gsc.tab=0
一种常见的方法是使用双括号。
width=540
new_width=$((${width} * 2)) # 它就是1080
运行其他代码时打印完整命令并运行
下面这样可以打印待运行命令并运行:
cmd="python3 run_abc.py ${abc} --opt 123"
echo $cmd
eval $cmd
查看空间
# 查看所有盘的空间(已使用空间和剩余空间等)
df -h
# 查看当前文件夹所占空间大小
du -sh .
if-else
简单例子:
if [ "${with_audio}" = true ]; then
cmd=${cmd}" --merge_audio_format wav"
fi
另一个例子:
#!/usr/bin/env bash
if [ "$(uname)" == "Darwin" ]; then
# Do something under Mac OS X platform
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
# Do something under GNU/Linux platform
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
# Do something under 32 bits Windows NT platform
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
# Do something under 64 bits Windows NT platform
fi
一个在终端输出带颜色文字的例子
# !bin/sh
# Define functions to print text in different colors
# Function to show colored text on screen. Example to print a text in red:
# cecho red "abcd"
cecho() {
local code="\033["
case "$1" in
black | bk) color="${code}0;30m";;
red | r) color="${code}1;31m";;
green | g) color="${code}1;32m";;
yellow | y) color="${code}1;33m";;
blue | b) color="${code}1;34m";;
purple | p) color="${code}1;35m";;
cyan | c) color="${code}1;36m";;
gray | gr) color="${code}0;37m";;
*) local text="$1"
esac
[ -z "$text" ] && local text="$color$2${code}0m"
echo "$text"
}
# 下面这些函数可以打印出带颜色文字,用法和 echo 完全相同,例如 printRed "hello world"
printRed() {
cecho red "$1"
}
printBlue() {
cecho blue "$1"
}
printCyan() {
cecho cyan "$1"
}
printGreen() {
cecho green "$1"
}
printYellow() {
cecho yellow "$1"
}
printPurple() {
cecho purple "$1"
}
在一个脚本中使用其他脚本的函数
最简单的方法是在最开头加上:
source other_script.sh
这样就可以直接用 other_script.sh
中定义的函数了。
函数中输入参数设置一个默认值
Ref: https://stackoverflow.com/questions/9332802/how-to-write-a-bash-script-that-takes-optional-input-arguments
类似这样:${1:-'jpg'}
,即在参数index后用冒号,然后用dash,紧接着就是默认值了。
一个完整的例子:
merge_image_frames_to_video_with_caption() {
image_folder=$1
caption_text=$2
output_video=$3
image_format=${4:-'jpg'}
caption_size=${5:-30}
cmd="ffmpeg -r 30 -i ${image_folder}/\"frame_%06d.${image_format}\" -y -hide_banner -c:v libx264 -pix_fmt yuv420p -vf \"drawtext=text=\'%{frame_num}\': fontfile=Hack-Regular.ttf: start_number=1: x=(w-tw)/2: y=0: fontcolor=black: fontsize=30: box=1: boxcolor=white: boxborderw=5, drawtext=text=\'${caption_text}\': fontfile=Hack-Regular.ttf: x=(w-tw)/2: y=h-(2*th): fontcolor=black: fontsize=${caption_size}: box=1: boxcolor=white: boxborderw=5\" ${output_video}"
echo $cmd
eval $cmd
}
这里 image_format 使用默认值是一个 string,而 caption_size 默认值是整数。