SHELL 规范
在总结过程中,发现更更牛逼的大神总结。
开头
开头是设置错误、异常,管道退出时的命令内容
set -e 等同于set -o errexit
配置脚本错误规范
# Exit on error. Append "|| true" if you expect an error.
set -o errexit
# Exit on error inside any functions or subshells.
set -o errtrace
# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR
set -o nounset
# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip`
set -o pipefail
# Turn on traces, useful while debugging but commented out by default
# set -o xtrace
路径调整
-
切换到脚本的当前路径
cd $(dirname "${BASH_SOURCE}")
获取当前路径的正确写法
pushd `dirname $0` > /dev/null SCRIPT_PATH=`pwd -P` popd > /dev/null
-
切换到脚本所在的上级目录
cd $(dirname "${BASH_SOURCE}")/..
-
切换到脚本所在目录的上级目录并把绝对路径给参数_PATH
_PATH=$(cd $(dirname "${BASH_SOURCE}")/.. && pwd)
- 目录绝对路径和脚本所在目录的绝对路径
_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" #看见$符号要有引号
_FILEPATH="${_DIR}/$(basename "${BASH_SOURCE[0]}")" #basename script.sh 获取脚本本身
_SCRIPTNO_SUFFIX="$(basename "${_FILEPATH}" .sh)" #拆分
- 相对路径和绝对路径取其一
_ABSOLUTE_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/
_UABPATH="${_ABSOLUTE_PATH:-.}/conf/fun.conf"
- 经典样式
set -e
pushd `dirname $0` > /dev/null
SCRIPTPATH=`pwd -P`
popd > /dev/null
SCRIPTFILE=`basename $0`
日志及显示格式
模块版本
日志格式是为了显示日志来写的,可以直接使用。
可以把本地代码写成文件的方式logconfig.sh
,通过
source ./logconfig.sh
进行加载。
使用的方式: 直接调用函数 info "content string"
##############################################################################
# Define the environment variables (and their defaults) that this script depends on
LOG_LEVEL=7 #兼容日志的最高级别,级别7以下的都会显示。
LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency 默认
NO_COLOR="${NO_COLOR:-}" # true = disable color. otherwise autodetected
### Functions
function __b3bp_log () {
## 这里读取的字段来自于 以下函数里的 第一个字段,例如:
## [[ "${LOG_LEVEL:-0}" -ge 1 ]] && __b3bp_log alert "${@}"; true;
## 获取的是 “alert” 的字段。
local log_level="${1}"
shift
# shellcheck disable=SC2034
local color_debug="\x1b[35m"
# shellcheck disable=SC2034
local color_info="\x1b[32m"
# shellcheck disable=SC2034
local color_notice="\x1b[34m"
# shellcheck disable=SC2034
local color_warning="\x1b[33m"
# shellcheck disable=SC2034
local color_error="\x1b[31m"
# shellcheck disable=SC2034
local color_critical="\x1b[1;31m"
# shellcheck disable=SC2034
local color_alert="\x1b[1;33;41m"
# shellcheck disable=SC2034
local color_emergency="\x1b[1;4;5;33;41m"“”
local colorvar="color_${log_level}" ##拼接上面是字符例如“color_critical”
local color="${!colorvar:-${color_error}}" ##没有日志级别,默认是error级别。
local color_reset="\x1b[0m"
if [[ "${NO_COLOR:-}" = "true" ]] || ( [[ "${TERM:-}" != "xterm"* ]] && [[ "${TERM:-}" != "screen"* ]] ) || [[ ! -t 2 ]]; then
if [[ "${NO_COLOR:-}" != "false" ]]; then
# Don't use colors on pipes or non-recognized terminals
color=""; color_reset=""
fi
fi
# all remaining arguments are to be printed
local log_line=""
while IFS=$'\n' read -r log_line; do
echo -e "$(date -u +"%Y-%m-%d %H:%M:%S UTC") ${color}$(printf "[%9s]" "${log_level}")${color_reset} ${log_line}" 1>&2
done <<< "${@:-}"
}
##支持的函数包含这么几种
function emergency () { __b3bp_log emergency "${@}"; exit 1; }
function alert () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && __b3bp_log alert "${@}"; true; }
function critical () { [[ "${LOG_LEVEL:-0}" -ge 2 ]] && __b3bp_log critical "${@}"; true; }
function error () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && __b3bp_log error "${@}"; true; }
function warning () { [[ "${LOG_LEVEL:-0}" -ge 4 ]] && __b3bp_log warning "${@}"; true; }
function notice () { [[ "${LOG_LEVEL:-0}" -ge 5 ]] && __b3bp_log notice "${@}"; true; }
function info () { [[ "${LOG_LEVEL:-0}" -ge 6 ]] && __b3bp_log info "${@}"; true; }
function debug () { [[ "${LOG_LEVEL:-0}" -ge 7 ]] && __b3bp_log debug "${@}"; true; }
简单版本
定义几个脚本颜色块,然后直接引用就好了。
COLOR_NONE='\033[0m'
COLOR_INFO='\033[0;36m'
COLOR_ERROR='\033[1;31m'
##直接覆盖加引用就可以了
echo -e "${COLOR_ERROR}Invalid option. Please input a number.${COLOR_NONE}"
交互式Parse 读取命令行参数里内容 method1
#这里是把所有的所有的 内容读取到文件里,如果没读成功,那么就显示'',如果读取成功,就显示‘true’,这样给用户显示 参数用法。
#[[ "${__usage+x}" ]] 高级用法 ${__usage} 是这个读取到这个内容的这个文件的临时函数,f [[ -n x ]]
# exits non-zero when EOF encountered
[[ "${__usage+x}" ]] || read -r -d '' __usage <<-'EOF' || true
-f --file [arg] Filename to process. Required.
-t --temp [arg] Location of tempfile. Default="/tmp/bar"
-v Enable verbose mode, print script as it is executed
-d --debug Enables debug mode
-h --help This page
-n --no-color Disable color output
-1 --one Do just one thing
EOF
## 可以通过 调用。
echo ${__usage}
###也可以根据
while read -r _temp_line; do
echo "print line ${_temp_line}" #逐行打印
if [[ "${_temp_line}" =~ ^- ]]; then #读取每行的,正则匹配,开头是“-”,
# fetch single character version of option string
_temp_line="${_temp_line%% *}" #模式匹配,匹配以空格为分解符的第一个所有的。
_temp_line="${_temp_line:1}" #截取模式,截取第一个字符后的其它字符,把“-” 去掉。
__b3bp_tmp_long_opt=""
if [[ "${_temp_line}" = "*__*" ]];then
__b3bp_tmp_long_opt=${_temp_line#*--}#拿出匹配的字符"debug Enables debug mode"
__b3bp_tmp_long_opt=${_temp_line%% *} #拿出 “debug”
fi
done <<< "${__usage:-}"
#done <<< ${parse_info} #这样表示整块读进去
EOF用法二
新建文件,并写入整块内容。可以执行整块的文本操作
cat <<EOF | sudo tee /root/influxdb.repo
#写入如下内容
[influxdb]
name = InfluxDB Repository - RHEL \$releasever
baseurl = https://repos.influxdata.com/rhel/\$releasever/\$basearch/stable
enabled = 1
gpgcheck = 1
gpgkey = https://repos.influxdata.com/influxdb.key
EOF
交互式Parse 读取命令行参数里内容 method2 简单版本
这个版本不能提取参数后定义的变量
方法一:
function usage() {
echo -e "Usage:"
echo -e "\t -a | --all : run all steps"
echo -e "\t -c | --config : choose a cluster.cfg file."
echo -e "\t -h | --help : this help usage."
}
while [ "${1}" != "" ]; do
case $1 in
-a | -all )
usage
;;
-h | -help )
usage
break
;;
-n | -number)
echo `cd $(dirname "${BASH_SOURCE}")/ && PWD`
;;
-f | -file )
exit 1
;;
esac
shift
done
方法二:
function usage(){
echo -e "Usage:"
echo -e "\t -a | --all : run all steps"
echo -e "\t -c | --config : choose a cluster.cfg file."
echo -e "\t -h | --help : this help usage."
}
while getopts 'h::v:n:' flag; do
case "${flag}" in
h) showUsage; exit ;;
n) name=${OPTARG} ;;
v) IMAGE_VERSION="${OPTARG}";;
n) usage;;
esac
done
获取脚本里的参数
#!/usr/bin/env bash
usage() { echo "$0 usage:" && grep " .)\ #" $0; exit 0; } # usage 函数。非常有意思,非常精巧。 $0 是函数名称,然后 grep 这个脚本里的 匹配“.)\ #” 行,正好为 一下case 里,写#的关键行。
[ $# -eq 0 ] && usage #没有任何参数时执行。
#h -检查 -h 不带参数
#h: -检查 -h 带参数,不给参数就报错
#abc -分别检查 -a -b -c 不给参数就报错
#:abc 分别检查 -a -b -c, 不给参数,就显示静默错误。
#$OPTARG 用来获取参数里的字段 例如 sh test.sh -p teststring -s 50; 其中 teststring 和 50 都会被获取
while getopts ":hs:p:" arg; do #获取关键字
case $arg in
p) # Specify p value.
echo "p is ${OPTARG}"
;;
s) # Specify strength, either 45 or 90.
strength=${OPTARG}
[[ "${strength}" -eq 45 -o "${strength}" -eq 90 ]] \
&& echo "Strength is $strength." \
|| echo "Strength needs to be either 45 or 90, $strength found instead."
;;
h ) # Display help.
usage
exit 0
;;
\?) # nothing to match
echo "Invalid option: -${OPTARG}" >&2
exit 0
;;
:) #匹配静默错误
echo "Option ${OPTARG} requires an argument." >&2
exit 0
;;
esac
done
shell 数组的表示
shell 数组非常方面,中间是空格就可以了
Fruits=('Apple' 'Banana' 'Orange')
echo ${Fruits[0]} # 第一个元素
echo ${Fruits[@]} # 所有元素的,空格分割 All elements, space-separated
echo ${#Fruits[@]} # 元素的个数 Number of elements
echo ${#Fruits} # 第一个元素字符串长度 String length of the 1st element
echo ${#Fruits[3]} # 第N个元素字符串长度 String length of the Nth element
echo ${Fruits[@]:3:2} # 第三个开始,长度为2 的数组 Range (from position 3, length 2)
数组的操作方法:
Fruits=("${Fruits[@]}" "Watermelon") # 增加一个元素 Push
Fruits+=('Watermelon') # 第二中增加方法 Also Push
Fruits=( ${Fruits[@]/Ap*/} ) # 移除一个匹配的 Remove by regex match
unset Fruits[2] # 移除指定元素 Remove one item
Fruits=("${Fruits[@]}") # 赋值一份 Duplicate
Fruits=("${Fruits[@]}" "${Veggies[@]}") # 连接一份 Concatenate
lines=(`cat "logfile"`) # Read from files's
shell SELECT 交互式,非常有技巧
获取用户交互的用read
echo "Enter your name"
read name
if [[ ! ${name} -z ]];then
echo "---"
fi
技巧大法例子
options=( $(find ${SCRIPTPATH} -maxdepth 1 -name "filename" -print0 | xargs -0) )
echo ${options[@] } # 获取匹配的所以是字段;当然options 可以是数组,定义 options=("agrument1" "agrusment2")
PS3="Please select a filename file: " ##专业写法,必须为PS3才有提示。默认为(#?)
re='^[0-9]+$' #匹配正则表达式,字母开头
select opt in "${options[@]}" "Quit" ; do #以匹配的每个文件,自动排序生成序号,并且新增的“Quit” 字符串也自动的添加序号。
if ! [[ $REPLY =~ $re ]] ; then # REPLY默认读取 read 里的参数。
echo -e "${COLOR_ERROR}Invalid option. Please input a number.${COLOR_NONE}"
elif (( REPLY == 1 + ${#options[@]} )) ; then #输入序号大于参数个数,${#options[@]} 值得是options 里参数的个数
exit
elif (( REPLY > 0 && REPLY <= ${#options[@]} )) ; then #输入的小于0
break
else
echo -e "${COLOR_ERROR}Invalid option. Try another one.${COLOR_NONE}" #其它情况
fi
done
echo -e -n "Are you sure the \033[4;31m${opt} is the right config file? (y/n) " #获取用户选择的结果
read -n 1 -r #读取用户结果
echo
if [[ ! ${REPLY} =~ ^[Yy]$ ]] ; then #匹配
# handle exits from shell or function but don't exit interactive shell
echo "Are you enter ${REPLY}?"
fi
##### 例子二
```shell
set -e
PS3=" Please select login host:"
_INPUT='^[0-9]+$'
#select opt in "${Host[@]}" "quit" ;do
select opt in "node1" "node2" "node3" "admin" "quit" ;do
if ! [[ "$REPLY" =~ ${_INPUT} ]];then
echo "Invalid option, please in put number "
elif (( REPLY == 5 )) ; then #输入序号大于参数个数
echo "Match 'quit', bye~"
exit
elif (( REPLY > 0 && REPLY <= 5 )) ;then #匹配规则
echo "OK,Wait for a moment~ "
break
else
echo "Invalid option. Try another one."
fi
done
function MatchIP() {
if [[ ! -z "${HOSTNAME}" ]];then
#echo ${HOSTNAME} "==========="
if [[ ${HOSTNAME} =~ "NODE1" ]];then
IP="172.16.97.218"
fi
ssh root@${IP}
else
echo "HostName not found!"
fi
}
case ${opt} in
node1 | 1 )
HOSTNAME="NODE1"
MatchIP #调用函数
;;
esac
技巧
判断值, 直接用[[ "${_var:-} "]] 判断值有否,如果有值则相当于 -n ,不为0 返回值为false; 参数无值,并替换为空值,则返回 true,等式成立,继续执行continue 。
${__variable:-} 如果变量存在且不为空则返回值,否则返回空;并判断 [[ -n ""]]是否为空。
[[ "${__variable:-}" ]] || continue
__variable=""
if [[ -n ${__variable} ]];then
continue
fi
case shout
${variable:-"argus"}
#这种不能单独使用,必须在表达式里或者传给其它变量值。
#因为是return 返回的,直接使用argus必须可执行才行。
#i.
_temp_name=${variable:-"argus"}
echo "${_temp_name}"
#ii.
[[ "${variable:-"argus"}" ]] && exit
find 的技巧
-print0 被NUL终结的字符串. ${argus[@]} 取出所有的值
_all_file="($(find ./ -maxdepth 1 -name "filename" -print0 | xargs -0))"
echo "${_all_file[@]}"
网友评论