shell/bash其实就是我们日常在unix系统终端中执行的语句,只是通常我们在命令行中都是单行语句执行的,而有时,我们希望将一些操作命令写到一个文本中,让电脑自动按顺序或是并行地执行这些命令,这样我们就不需要时刻守在电脑前一行行去执行命令了。
  

1 文件构成

1.1 文件后缀

  shell文件通常以.sh为后缀,如test.sh,其实质是一个文本文件。
  

1.2 指定解析器

  通常,文件的第一行是该文件所需要使用的解析器的定义,一般使用bash,命令如下:

#!/bin/bash

  

1.3 运行脚本

  比如要运行名为test.sh的脚本文件,首先我们先将命令行路径进入到该脚本文件所在路径下,有三种形式(任选其一即可):

./test.sh        # 先按照文件中#!指定的解析器解析
bash test.sh     # 先用bash解析器解析
. test.sh        # 直接使用默认解析器解析

  在运行的时候,可能你会遇到如下报错:

-bash: ./test.sh: Permission denied

  说明test.sh文件还没有执行权限,我们通过命令行给它加上执行权限:

chmod +x test.sh

  之后再运行test.sh文件就可以了。
  

2 常用命令

2.1 注释 -

  单行注释与python语言的一致,由单个#开始。
  

2.2 输出 - scho

  语法格式:

echo hello world

  上面都没有给出运行示例,现在,让我们打开新建的test.sh文件,在里面输入如下内容:

bash脚本查询hive bash脚本执行命令_bash


  然后打开命令行,执行文件,就可以看到输出了:

bash脚本查询hive bash脚本执行命令_bash_02


  

2.3 变量定义与使用

  shell里的变量不需要指明变量类型,类似于python,可以直接进行声明和赋值。比如:

myage=18    # 声明一个变量myage,值为20

  但是,需要注意如下两点:

  1. 等号前后是没有空格的
  2. 变量没有类型之分,都默认为string类型,即myage=18myage=“18”在使用的时候都是一样的

  变量的使用方式有两种:

  1. 直接$变量名
  2. ${变量名}

  这两种方式都是可以的,第2种方式在变量名前后加上{},主要是可以界定变量名的范围。比如使用上面定义的变量:

echo I am ${myage}years old
# 输出为I am 18years old

  但是如果没有使用花括号:

echo I am $myageyears old
# 输出为I am old

  

2.4 扩展计算 - (())

  (( ))是一种扩展运算符,只要符合C语言标准的语句都可以在这里面执行。比如使用上面定义的myage变量,现在我要让它自增1。如果直接写:

myage+=1
# 输出myage,会发现结果是181,也就是被当成字符串运算了

  而要获得正确结果,应该写成:

((myage++))
((myage+=1))
# 以上两种写法都可以,结果均为19

  需要注意的一点是,shell只能作整数运算,对于浮点数都是直接当作字符串处理的。
  此外,除了(( ))之外,shell中还有( )[ ][[ ]]等等括号,用法不尽相同,可以参考:shell中各种括号的作用()、(())、[]、[[]]、{}   

2.5 多语句同行 - ;

  要将多个不同的语句放在同一行,需要在语句末加上;进行分隔。比如:

myage=18; echo I am ${myage}years old

  

2.6 续行 - \

  如果一个语句太长了影响阅读,可以通过\将一行语句写为多行。比如:

python test.py --config config.txt \
               --input data.txt \
               --output output.txt

  这是一个运行python文件的命令,这里将其分为三行。注意这里续行符的左边都有一个空格,这样的话这三行命令就等同于如下一行命令了:

python test.py --config config.txt --input data.txt --output output.txt

  也即不同参数之间存在一个空格。如果没有了续行符左侧的空格,就会等同于:

python test.py --config config.txt--input data.txt--output output.txt

  这里,续行符左侧的每个空格和实际空格是一一对应的,如果有两个空格,那将命令放在一行的时候也是两个空格。但是续行符右侧的空格数量和换行之后命令前的空格数量都是不影响命令的,比如:

python test.py --config config.txt \
--input data.txt \
--output output.txt

  这样写命令也是可以的。
  

2.7 循环 - for

  for循环的一般格式为:

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

  如果要将其写成一行,则是:

for var in item1 item2 ... itemN; do command1; command2… done;

  比如:

for loop in 1 2 3 4 5
do
    echo "The value is: $loop"
done

  输出结果为:

The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5

  除了这种方式外,类似于C语言的for循环也是可以的,比如:

for((i=1;i<=5;i++))
do
{
  echo $i
}
done

  输出的结果为:

1
2
3
4
5

  需要注意的是,这里for循环需要两个小括号(扩展C语言计算),不能将其中一个去掉,否则会报语法错误。
  

2.8 选择 - if-else

  一般格式为:

if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi

  其中最后的fiif语句结束的标记,elifelse都是可选的。
  一个例子:

a=10
b=20
if (( $a == $b ))
then
   echo "a 等于 b"
elif (( $a > $b ))
then
   echo "a 大于 b"
elif (( $a < $b ))
then
   echo "a 小于 b"
else
   echo "没有符合的条件"
fi

  输出为:

a 小于 b

  

2.9 并行执行 - &

  shell脚本默认是串行执行命令的,也就是只有当上一行语句运行完成后才会运行下一行命令。但是有时候,我们希望命令是一起执行的。比如,我的服务器上有多张GPU,想在每张GPU上都跑不同的任务,它们之间互不相干,因此可以同步执行。并行执行的方法也很简单,就是直接在上一行命令的末尾加上&符号即可。比如:

python test1.py&
python test2.py&
python test3.py

  如果使用for循环,也可以简单地写为:

for((i=1;i<=3;i++))
do
{
  python test${i}.py
};
done

  

2.10 等待 - wait

  使用&运算符可以使命令同步执行,但是有时,下一步的程序是需要依靠上一步的执行结果来作为输入的。那么,我们需要确保上一步完全执行完毕了,才运行下一步的命令。这时,我们可以使用wait关键字。比如:

python test1.py
wait
python test2.py

  这样,就只能在test1完成后,才会执行test2任务了。
  

2.11 暂停 - sleep

  sleep命令可以使命令行暂停一定时间再执行。比如:

python test1.py
sleep 10
python test2.py

  这样,test1执行完之后,会等待10s,之后才执行test2。

shell/bash命令还有丰富的语法内容,这里只列出一些基本的命令,可以供日常简单的使用。如果还需要更复杂高级的应用,可以寻找专业的资料进一步学习。

  

3 一个实例

  根据上述内容,我写出了如下脚本文件,该文件用于运行mega-nerf网络中。

#!/bin/bash

##################################################### variables ########################################################

# experiment id
expid=14

# colmap sparse model path, where contain cameras,images,points3D
modelpath=../../data166/ISPRS1/ISPRS1/rescale_2/sparse
# input scene images path
imagespath=../../data166/ISPRS1/ISPRS1/rescale_2/images
# image number for validation
numval=24
# PCA rotation if coordinate is not aligned
pcarotate=--pca_rotate
# output meganerf files path
megapath=../../outdata/isprs/experiment${expid}/isprs_mega
# where contain rayrange message, can be generate automatically
configfile=../../outdata/isprs/experiment${expid}/rayrange.yaml

# mask path
maskpath=../../outdata/isprs/experiment${expid}/mask16
# grid dim
griddim1=4
griddim2=4

# scale to downsample images
trainscalefactor=2
# experiment prefix(where to store training results)
expprefix=../../outdata/isprs/experiment${expid}/exp
# chunk prefix(where to store chunk message)
chunkprefix=../../outdata/isprs/experiment${expid}/chunk
# where to save log files(with prefix)
logdir=../../outdata/isprs/experiment${expid}/chunks
# depth guide path
depthmappath=../../outdata/depth

# checkpoint prefix(where store the network parameters)
ckptprefix=../../outdata/isprs/experiment${expid}/exp-
# centroid path
centroidpath=${maskpath}/params.pt
# to merge the models with how many training iterations
trainiterations=50000
# file path to store merged model
mergedpath=../../outdata/isprs/experiment${expid}/merged${trainiterations}.pt

# path to store evaluation results
evalpath=../../outdata/isprs/experiment${expid}/eval
# valuation scale
valscalefactor=8

##################################################### commands #########################################################

# colmap to meganerf
python scripts/colmap_to_mega_nerf.py \
      --model_path ${modelpath} \
      --images_path ${imagespath} \
      --num_val ${numval} \
      --output_path ${megapath} \
      --config ${configfile} \
      ${pcarotate}

wait

# generate clusters
python scripts/create_cluster_masks.py \
      --config ${configfile} \
      --dataset_path ${megapath} \
      --output ${maskpath} \
      --grid_dim ${griddim1} ${griddim2}

wait

# training: run_4
for ((i=0;i<4;i++))
do
{
  CONFIG_FILE=${configfile} \
  DATASET_PATH=${megapath} \
  MASK_PATH=${maskpath} \
  TRAIN_SCALE_FACTOR=${trainscalefactor} \
  EXP_PREFIX=${expprefix} \
  CHUNK_PREFIX=${chunkprefix} \
  DEPTHMAP_PATH=${depthmappath} \
  nohup python -m parscript.dispatcher parscripts/run_4_${i}.txt -g 8 > ${logdir}${i}.log 2>&1 &
}&
done

wait

# merge models
python scripts/merge_submodules.py \
      --config_file ${configfile} \
      --ckpt_prefix ${ckptprefix} \
      --centroid_path ${centroidpath} \
      --train_iterations ${trainiterations} \
      --output ${mergedpath}

wait

# evaluate model
python mega_nerf/eval.py \
      --config_file ${configfile} \
      --exp_name ${evalpath} \
      --dataset_path ${megapath} \
      --container_path ${mergedpath} \
      --val_scale_factor ${valscalefactor}

  
部分内容参考:
shell脚本的使用入门(超全)

Shell 流程控制