背景

博客: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. 【Java】手把手理解CAS实现原理

    先来看看概念,[CAS] 全称“CompareAndSwap”,中文翻译即“比较并替换”. 定义:CAS操作包含三个操作数 —— 内存位置(V),期望值(A),和新值(B). 如果内存位置的值与期望值 ...

  2. Higher-Order Functions Fundamentals

    Higher-Order Functions A function that accepts and/or returns another function is called a higher-or ...

  3. Swiper的jquery动态渲染不能滑动

    <!-- 下面俩行代码就是解决异步加载数据导致swiper不轮播的关键 --> observer: true,//修改swiper自己或子元素时,自动初始化swiper observePa ...

  4. 基于Unity实现像素化风格的着色器

    Shader "MyShaderTest/SimplePixelationShader" { Properties { _MainTex ("Base (RGB)&quo ...

  5. Java 泛型与集合

    1.List练习,请用泛型的写法来完成. 已知有一个Worker 类如下: public class Worker  { private int age; private String name; p ...

  6. Jmeter执行多个sql查询语句

    1.添加jdbc connection(注意标红部分) 2.添加jdbc request 3.查看结果树 本文主要向大家介绍了Oracle数据库之jmeter jdbc request 如何运行多个s ...

  7. RocketMQ安装及入门

    本文是作者原创,版权归作者所有.若要转载,请注明出处. 本文RocketMQ版本为rocketmq-all-4.7.0,系统为win10.请各位去官网下载,也可以留言,我发安装包 RocketMQ安装 ...

  8. 完美解决报错Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'

    Failed to convert value of type 'java.lang.String' to required type 'java.util.Date' 首先这个错误的意思是 前台页面 ...

  9. 利用Nginx设置跨域的方式

    1.服务端可控,添加响应头 2.服务端不可控.通过Nginx反向代理 3.服务端不可控.通过Nginx反向代理添加响应头 第一种方法.服务端可控时,可以在服务器端添加响应头(前端+后端解决) 浏览器地 ...

  10. ASP.NET Core MVC+Layui使用EF Core连接MySQL执行简单的CRUD操作

    前言: 本章主要通过一个完整的示例讲解ASP.NET Core MVC+EF Core对MySQL数据库进行简单的CRUD操作,希望能够为刚入门.NET Core的小伙伴们提供一个完整的参考实例.关于 ...