Shell Script 使用笔记

简介

参考:

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 默认值是整数。

Search

    Table of Contents