背景

博客:https://www.cnblogs.com/Rohn

使用哪一种Shell

可执行文件必须以 #!/bin/bash 和最小数量的标志开始。请使用 set 来设置shell的选项,使得用 <script_name>调用你的脚本时不会破坏其功能。

推荐使用:

#!/usr/bin/env bash

env一般固定在/usr/bin目录下,而其余解释器的安装位置就相对不那么固定。

限制所有的可执行Shell脚本为bash使得我们安装在所有计算机中的shell语言保持一致性。

无论你是为什么而编码,对此唯一例外的是当你被迫时可以不这么做的。其中一个例子是Solaris SVR4包,编写任何脚本都需要用纯Bourne shell

[root@test ~]# echo $SHELL
/bin/bash

什么时候使用Shell

使用Shell需要遵守的一些准则:

  • 如果你主要是在调用其他的工具并且做一些相对很小数据量的操作,那么使用Shell来完成任务是一种可接受的选择。
  • 如果你在乎性能,那么请选择其他工具,而不是使用Shell。
  • 如果你发现你需要使用数据而不是变量赋值(如 ${PHPESTATUS} ),那么你应该使用Python脚本。
  • 如果你将要编写的脚本会超过100行,那么你可能应该使用Python来编写,而不是Shell。

请记住,当脚本行数增加,尽早使用另外一种语言重写你的脚本,以避免之后花更多的时间来重写。

注释

博客:https://www.cnblogs.com/Rohn

Bash只支持单行注释,使用#开头的都被当作注释语句。

顶层注释

每个文件必须包含一个顶层注释,对其内容进行简要概述。版权声明和作者信息是可选的。

例如:

#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Created Time: 2020/06/06
# Perform hot backups of MySQL databases.
  • 第1行,指明解释器,使用bash

#!叫做"Shebang"或者"Sha-bang"(Unix术语中,#号通常称为sharp,hash或mesh;而!则常常称为bang),指明了执行这个脚本文件的解释程序。当然,如果使用bash test.sh这样的命令来执行脚本,那么#!这一行将会被忽略掉。

  • 第2-5行,分别为作者、版本号、创建时间、功能说明。

功能注释

任何不是既明显又短的函数都必须被注释。任何库函数无论其长短和复杂性都必须被注释。

其他人通过阅读注释(和帮助信息,如果有的话)就能够学会如何使用你的程序或库函数,而不需要阅读代码。

所有的函数注释应该包含:

  • 函数的描述
  • 全局变量的使用和修改
  • 使用的参数说明
  • 返回值,而不是上一条命令运行后默认的退出状态

例如:

#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Created Time: 2020/06/06
# Perform hot backups of Oracle databases. export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin' #######################################
# Cleanup files from the backup dir
# Globals:
# BACKUP_DIR
# ORACLE_SID
# Arguments:
# None
# Returns:
# None
#######################################
cleanup() {
...
}

TODO注释

TODOs应该包含全部大写的字符串TODO,接着是括号中你的用户名。冒号是可选的。最好在TODO条目之后加上bug或者ticket的序号。

例如:

# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)

格式

博客:https://www.cnblogs.com/Rohn

缩进

缩进两个空格,没有制表符。例如:

if [ a > 1 ];then
echo '${a} > 1'
fi

行的长度和长字符串

行的最大长度为80个字符。例如:

# DO use 'here document's
cat <<END;
I am an exceptionally long
string.
END # Embedded newlines are ok too
long_string="I am an exceptionally
long string."

管道

如果一行容不下整个管道操作,那么请将整个管道操作分割成每行一个管段。

应该将整个管道操作分割成每行一个管段,管道操作的下一部分应该将管道符放在新行并且缩进2个空格。这适用于使用管道符|的合并命令链以及使用||&&的逻辑运算链。

例如:

# All fits on one line
command1 | command2 # Long commands
command1 \
| command2 \
| command3 \
| command4

循环

if-else语句

if; then放在同一行,;后空一格,else单独一行,fi单独一行,并与if垂直对齐。即:

if condition; then
statement(s)
else
statement(s)
fi

for-do和while-do语句

while/for; do放在同一行,donewhile/for垂直对齐,即:

# while structure
while condition; do
statement(s)
done # for structure
for condition; do
statement(s)
done

例如:

for dir in ${dirs_to_cleanup}; do
if [[ -d "${dir}/${ORACLE_SID}" ]]; then
log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
rm "${dir}/${ORACLE_SID}/"*
if [[ "$?" -ne 0 ]]; then
error_message
fi
else
mkdir -p "${dir}/${ORACLE_SID}"
if [[ "$?" -ne 0 ]]; then
error_message
fi
fi
done

case语句

  • 通过2个空格缩进可选项。
  • 在同一行可选项的模式右圆括号之后和结束符 ;;之前各需要一个空格。
  • 长可选项或者多命令可选项应该被拆分成多行,模式、操作和结束符;;在不同的行。

匹配表达式比caseesac 缩进一级。多行操作要再缩进一级。一般情况下,不需要引用匹配表达式。模式表达式前面不应该出现左括号。避免使用;&;;&符号。即:

# case structure
case in expression in
pattern1)
statement1
;;
pattern2)
statement2
;;
...
*)
statementn
;;
esac

例如:

case "${expression}" in
a)
variable="..."
some_command "${variable}" "${other_expr}" ...
;;
absolute)
actions="relative"
another_command "${actions}" "${other_expr}" ...
;;
*)
error "Unexpected expression '${expression}'"
;;
esac

只要整个表达式可读,简单的命令可以跟模式和;; 写在同一行。这通常适用于单字母选项的处理。当单行容不下操作时,请将模式单独放一行,然后是操作,最后结束符;; 也单独一行。当操作在同一行时,模式的右括号之后和结束符;;之前请使用一个空格分隔。

verbose='false'
aflag=''
bflag=''
files=''
while getopts 'abf:v' flag; do
case "${flag}" in
a) aflag='true' ;;
b) bflag='true' ;;
f) files="${OPTARG}" ;;
v) verbose='true' ;;
*) error "Unexpected option ${flag}" ;;
esac
done

变量扩展

按优先级顺序:保持跟你所发现的一致;引用你的变量;推荐用${var}而不是$var

例如

# Section of recommended cases.

# Preferred style for 'special' variables:
echo "Positional: $1" "$5" "$3"
echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ ..." # Braces necessary:
echo "many parameters: ${10}" # Braces avoiding confusion:
# Output is "a0b0c0"
set -- a b c
echo "${1}0${2}0${3}0" # Preferred style for other variables:
echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
while read f; do
echo "file=${f}"
done < <(ls -l /tmp) # Section of discouraged cases # Unquoted vars, unbraced vars, brace-quoted single letter
# shell specials.
echo a=$avar "b=$bvar" "PID=${$}" "${1}" # Confusing use: this is expanded as "${1}0${2}0${3}0",
# not "${10}${20}${30}
set -- a b c
echo "$10$20$30"

特性

博客:https://www.cnblogs.com/Rohn

命令替换

使用 $(command)而不是反引号。

嵌套的反引号要求用反斜杠转义内部的反引号。而$(command) 形式嵌套时不需要改变,而且更易于阅读。

例如:

# This is preferred:
var="$(command "$(command1)")" # This is not:
var="`command \`command1\``"

文件名的通配符扩展

当进行文件名的通配符扩展时,请使用明确的路径。

因为文件名可能以-开头,所以使用扩展通配符./**来得安全得多。

# Here's the contents of the directory:
# -f -r somedir somefile # This deletes almost everything in the directory by force
psa@bilby$ rm -v *
removed directory: `somedir'
removed `somefile' # As opposed to:
psa@bilby$ rm -v ./*
removed `./-f'
removed `./-r'
rm: cannot remove `./somedir': Is a directory
removed `./somefile'

命名约定

博客:https://www.cnblogs.com/Rohn

函数名

使用小写字母,并用下划线分隔单词。使用双冒号 :: 分隔库。函数名之后必须有圆括号。关键词 function 是可选的,但必须在一个项目中保持一致。

如果你正在写单个函数,请用小写字母来命名,并用下划线分隔单词。如果你正在写一个包,使用双冒号 :: 来分隔包名。大括号必须和函数名位于同一行(就像在Google的其他语言一样),并且函数名和圆括号之间没有空格。

# Single function
my_func() {
...
} # Part of a package
mypackage::my_func() {
...
}

当函数名后存在 () 时,关键词 function 是多余的。但是其促进了函数的快速辨识。

变量名

使用小写字母,循环的变量名应该和循环的任何变量同样命名。例如:

for zone in ${zones}; do
something_with "${zone}"
done

常量和环境变量名

全部使用大写字母,用下划线分隔,声明在文件的顶部。例如:

# Constant
readonly PATH_TO_FILES='/some/path' # Both constant and environment
declare -xr ORACLE_SID='PROD'

源文件名

使用小写字母,如果需要的话使用下划线分隔单词。例如: maketemplate 或者 make_template ,而不是 make-template

只读变量

使用小写字母,使用 readonly 或者 declare -r 来确保变量只读。

因为全局变量在Shell中广泛使用,所以在使用它们的过程中捕获错误是很重要的。当你声明了一个变量,希望其只读,那么请明确指出。

zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
error_message
else
readonly zip_version
fi

使用本地变量

使用小写字母,使用 local 声明特定功能的变量。声明和赋值应该在不同行。

使用 local 来声明局部变量以确保其只在函数内部和子函数中可见。这避免了污染全局命名空间和不经意间设置可能具有函数之外重要性的变量。

当赋值的值由命令替换提供时,声明和赋值必须分开。因为内建的 local 不会从命令替换中传递退出码。

my_func2() {
local name="$1" # Separate lines for declaration and assignment:
local my_var
my_var="$(my_func)" || return # DO NOT do this: $? contains the exit code of 'local', not my_func
local my_var="$(my_func)"
[[ $? -eq 0 ]] || return ...
}

调用命令

博客:https://www.cnblogs.com/Rohn

检查返回值

对于非管道命令,使用$?或直接通过一个if语句来检查以保持其简洁。例如:

if ! mv "${file_list}" "${dest_dir}/" ; then
echo "Unable to move ${file_list} to ${dest_dir}" >&2
exit "${E_BAD_MOVE}"
fi # Or
mv "${file_list}" "${dest_dir}/"
if [[ "$?" -ne 0 ]]; then
echo "Unable to move ${file_list} to ${dest_dir}" >&2
exit "${E_BAD_MOVE}"
fi

Bash也有 PIPESTATUS 变量,允许检查从管道所有部分返回的代码。如果仅仅需要检查整个管道是成功还是失败,以下的方法是可以接受的:

tar -cf - ./* | ( cd "${dir}" && tar -xf - )
if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then
echo "Unable to tar files to ${dir}" >&2
fi

可是,只要你运行任何其他命令, PIPESTATUS 将会被覆盖。如果你需要基于管道中发生的错误执行不同的操作,那么你需要在运行命令后立即将 PIPESTATUS 赋值给另一个变量(别忘了 [ 是一个会将 PIPESTATUS 擦除的命令)。

tar -cf - ./* | ( cd "${DIR}" && tar -xf - )
return_codes=(${PIPESTATUS[*]})
if [[ "${return_codes[0]}" -ne 0 ]]; then
do_something
fi
if [[ "${return_codes[1]}" -ne 0 ]]; then
do_something_else
fi

Shell语法规范的更多相关文章

  1. shell编程规范:引用

    Shell代码规范 作 者: 毕小朋 用 途: 规范Shell代码书写,方便查看与修改 博 客: http://blog.csdn.net/wirelessqa 参 考: http://www.ohl ...

  2. JSLint检测Javascript语法规范

    前端javascript代码编写中,有一个不错的工具叫JSLint,可以检查代码规范化,压缩JS,CSS等,但是他的语法规范检查个人觉得太“苛刻”了,会提示各种各样的问题修改建议,有时候提示的信息我们 ...

  3. css 之 1.基本语法规范

    文章转自:http://www.10wy.net/Article/CSS/CSS_list_8.html查看更多更专业性的文章请到:网页设计网 第一篇 CSS 1.基本语法规范 分析一个典型CSS的语 ...

  4. makefile中的shell语法

    在Makefile中写shell代码有点诡异,和不同的shell语法不太一样,如果不了解,看Makefile会莫名其妙.下面总结了一些. 1:尽在Makefile文件的目标项冒号后的另起一行的代码才是 ...

  5. 【转】Application.mk 文件语法规范

    原文网址:http://blog.sina.com.cn/s/blog_4c451e0e0100s6q4.html Application.mk file syntax specification A ...

  6. MySQL数据库基础(一)(启动/停止、登录/退出、语法规范及最基础操作)

    1.启动/停止MySQL服务 启动:net start mysql    停止:net stop mysql 2.MySQL登录/退出 登录:mysql 参数:如果连接的是本地服务器,一般用命令:my ...

  7. web前端(14)—— JavaScript的数据类型,语法规范1

    编辑器选择 对js的编辑器选用,有很多,能对html编辑的,也能对js编辑,比如notepad++,visual studio code,webstom,atom,pycharm,sublime te ...

  8. RAP Mock.js语法规范

    Mock.js 的语法规范包括两部分: 数据模板定义规范(Data Template Definition,DTD) 数据占位符定义规范(Data Placeholder Definition,DPD ...

  9. JavaScript 中语法规范及调试

    JavaScript 中语法规范及调试 版权声明:未经博主授权,内容严禁分享转载 JavaScript 开发环境 JavaScript 脚本可以使用任意一款纯文本编辑器进行编程开发. 常见的前端开发编 ...

随机推荐

  1. python基本数据类型:字符串及其方法(二)

    格式化类 方法join() #join()用指定字符每间隔拼接字符串 name='miku' name1=' '.join(name) print(name1) 方法center() #center( ...

  2. Gym101630L Laminar Family

    题目链接:https://cn.vjudge.net/problem/Gym-101630L 题目大意: 对于一个集合的集合,若其中任意两个集合 \(A\) 和 \(B\) 都满足下述三个条件之一:\ ...

  3. Robot Framework(1)- 入门介绍

    如果你还想从头学起Robot Framework,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1770899.html RF 的介绍 R ...

  4. ES[7.6.x]学习笔记(十一)与SpringBoot结合

    在前面的章节中,我们把ES的基本功能都给大家介绍完了,从ES的搭建.创建索引.分词器.到数据的查询,大家发现,我们都是通过ES的API去进行调用,那么,我们在项目当中怎么去使用ES呢?这一节,我们就看 ...

  5. 7.SortSet排序集合类型操作

    Sort Set排序集合类型 (1)介绍 和set一样sorted set也是string类型元素的集合,不同的是每个元素都会关联一个权.通过权值可以有序的获取集合中的元素 该Sort Set类型适合 ...

  6. 我的Android知识结构图——20200507停止更新,后续通过标签或分类继续完善结构图

    *持续更新中.调整中(带链接的是已经总结发布的,未带链接是待发布的) *个别知识点在多个分类中都是比较重要部分,为了分类完整性 可能多出都列出了 *每一篇都是认真总结并写出来的,若哪里有问题欢迎指正 ...

  7. SpringBoot打包Docker镜像

    构建spring boot项目 本地测试访问 打成jar包 在本地运行jar包测试 到这一步就证明jar包没问题 idea下载一个插件 在这创建一个Dockerfile文件 安装插件后会高亮显示. 在 ...

  8. Java学习之路【第一篇】:前言

    Java 语言概述 一.什么是Java语言 Java语言是美国Sun公司(Stanford University Network),在1995年推出的高级的编程语言.所谓编程语言,是计算机的语言,人们 ...

  9. 关于String是值传递还是引用传递

    public class Itv { static String ss = "kkkkkk"; static String ss1 = new String("kkkkk ...

  10. Hadoop Yarn REST API未授权漏洞利用

    Hadoop Yarn REST API未授权漏洞利用 Hadoop是一个由Apache基金会所开发的分布式系统基础架构,YARN是hadoop系统上的资源统一管理平台,其主要作用是实现集群资源的统一 ...