版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/CSDN___LYY/article/details/100584638
在公司项目的开发过程中,需要编写shell脚本去处理一个业务,在编写过程中发现自身对shell脚本的知识不够完善,顾整理一下其基本语法,本文章主要内容来自菜鸟教程 , 也添加了一些知识点
看完这边文章应该就可以独立完成大部分脚本得编写,复杂脚本还需要一些其他不常用的操作,到时候自行google吧
在说什么是shell脚本之前,先说说什么是shell。
shell是外壳的意思,就是操作系统的外壳。我们可以通过shell命令来操作和控制操作系统,比如Linux中的Shell命令就包括ls、cd、pwd等等。总结来说,Shell是一个命令解释器,它通过接受用户输入的Shell命令来启动、暂停、停止程序的运行或对计算机进行控制。
shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。
shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序。
那么什么是shell脚本呢?
shell脚本就是由Shell命令组成的执行文件,将一些命令整合到一个文件中,进行处理业务逻辑,脚本不用编译即可运行。它通过解释器解释运行,所以速度相对来说比较慢。
shell脚本中最重要的就是对shell命令的使用与组合,再使用shell脚本支持的一些语言特性,完成想要的功能。
“# ”开头的就是注释,被编译器忽略
变量类型 运行shell时,会同时存在三种变量:
变量操作
字符串变量 1)单引号
var='test'
,只能原样输出,变量无效2)双引号
var="my name is ${name}"
,变量有效3)拼接字符串
4)获取字符串长度
5)提取子字符串
数组
bash只支持一维数组,不支持多维数组
算数运算
数字关系运算符 关系运算符只支持数字,不支持字符串,除非字符串的值是数字。 下面假定变量 a 为 10,变量 b 为 20
字符串运算符 下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:
布尔运算符 下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:
逻辑运算符 以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:
文件运算符
命令替换 命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。 执行命令:
for file in \s /etc\ 或 for file in $(ls /etc) 循环中使用 `dirname $0` 获取脚本文件所在的目录 path=$(cd `dirname $0`;pwd) : 获取脚本当前所在目录,并且执行cd命令到达该目录,使用pwd获取路径并赋值到path变量
算术运算
逻辑判断
echo 仅用于字符串的输出,没有使用printf作为输出的移植性好,建议使用printf
printf
printf 不会像 echo 自动添加换行符,我们可以手动添加 \n 无大括号,直接以空格分隔
printf format-string [arguments...]
其中(format-string: 格式控制字符串、arguments: 参数列表)printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
%-10s
: 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。%-4.2f
:指格式化为小数,宽度为4个字符,其中.2
指保留2位小数。和Java、PHP等语言不一样,sh的流程控制不可为空,即if或者else的大括号中无任何语句 if else
if condition
then
command1
command2
...
commandN
fi
if condition
then
command1
command2
...
commandN
else
command
fi
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
for
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
while
while condition
do
command
done
while :
do
command
done
until until 循环执行一系列命令直至条件为 true 时停止。 until 循环与 while 循环在处理方式上刚好相反。
until condition
do
command
done
case Shell case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。 case需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号,用两个分号表示break,其中“;;”不是跳出循环,是不在去匹配下面的模式 case语句格式如下:
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
跳出循环
可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
[ function ] funname()
{
action;
[return int;]
}
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
echo $? \# 判断执行是否成功
BIN=\
abs_path``语句,获取的是函数体内所有的echo、printf输出组合成的一个字符串abs_path() {
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
echo "test"
echo "$( cd -P "$( dirname "$SOURCE" )" && pwd )"
# 此函数的两个echo输出会组合成一个字符串作为下述BIN的值
}
BIN=`abs_path` # BIN赋值函数返回值,如果没有return,则函数中所有的echo、printf输出组合成一个字符串传入BIN
path=${BIN}/nodetool # 可直接使用
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。 如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
输入重定向
输出重定向
命令:read arg
(脚本读取外部输入并赋值到变量上)
在shell脚本执行到上述命令时,停止脚本执行并等待外部输入,将外部输入赋值到arg变量上,继续执行脚本
引用其他的文件之后,可以使用其变量、函数等等,相当于将引用的文件包含进了当前文件 两种方式:
printf "\033[32m SUCCESS: yay \033[0m\n";
printf "\033[33m WARNING: hmm \033[0m\n";
printf "\033[31m ERROR: fubar \033[0m\n";
输出结果:
在shell中为避免一个语句过长,可以使用“\”进行换行 使用“\”换行,在脚本执行过程中还是当做一行一个语句执行,不同于enter直接换行
注意:\ 前添加一个空格 。 \ 后无空格直接换行。
/mysql/bin/mysql \
-h test_host -P 000 \
-u test_user -ptest_password ;
下面案例为登录mysql,并选择操作数据库,之后进行导入数据
/mysql/mysql/bin/mysql \
-h test_host -P 000 \
-u test_user -ptest_password \
-e"use test_database; source data_faile; " # -e 代表执行sql语句
-u 用户名 -p 用户密码 -h 服务器ip地址 -D 连接的数据库 -N 不输出列信息 -B 使用tab键 代替 分隔符 -e 执行的SQL语句
命令:exit
在退出脚本时使用不同的错误码,这样可以根据错误码来判断发生了什么错误。
在绝大多数 shell 脚本中,exit 0 表示执行成功,exit 1 表示发生错误。 对错误与错误码进行一对一的映射,这样有助于脚本调试。
命令:set -e 或者 set +e
set -e表示从当前位置开始,如果出现任何错误都将触发exit。相反,set +e表示不管出现任何错误继续执行脚本。
如果脚本是有状态的(每个后续步骤都依赖前一个步骤),那么请使用set -e,在脚本出现错误时立即退出脚本。 如果要求所有命令都要执行完(很少会这样),那么就使用set +e。
这是es(ElasticSearch)官方启动服务的脚本,看可不可以理解吧~
#!/usr/bin/env bash
# CONTROLLING STARTUP:
#
# This script relies on a few environment variables to determine startup
# behavior, those variables are:
#
# ES_PATH_CONF -- Path to config directory
# ES_JAVA_OPTS -- External Java Opts on top of the defaults set
#
# Optionally, exact memory values can be set using the `ES_JAVA_OPTS`. Note that
# the Xms and Xmx lines in the JVM options file must be commented out. Example
# values are "512m", and "10g".
#
# ES_JAVA_OPTS="-Xms8g -Xmx8g" ./bin/elasticsearch
source "`dirname "$0"`"/elasticsearch-env
parse_jvm_options() {
if [ -f "$1" ]; then
echo "`grep "^-" "$1" | tr '\n' ' '`"
fi
}
ES_JVM_OPTIONS="$ES_PATH_CONF"/jvm.options
ES_JAVA_OPTS="`parse_jvm_options "$ES_JVM_OPTIONS"` $ES_JAVA_OPTS"
# manual parsing to find out, if process should be detached
if ! echo $* | grep -E '(^-d |-d$| -d |--daemonize$|--daemonize )' > /dev/null; then
exec \
"$JAVA" \
$ES_JAVA_OPTS \
-Des.path.home="$ES_HOME" \
-Des.path.conf="$ES_PATH_CONF" \
-cp "$ES_CLASSPATH" \
org.elasticsearch.bootstrap.Elasticsearch \
"$@"
else
exec \
"$JAVA" \
$ES_JAVA_OPTS \
-Des.path.home="$ES_HOME" \
-Des.path.conf="$ES_PATH_CONF" \
-cp "$ES_CLASSPATH" \
org.elasticsearch.bootstrap.Elasticsearch \
"$@" \
<&- &
retval=$?
pid=$!
[ $retval -eq 0 ] || exit $retval
if [ ! -z "$ES_STARTUP_SLEEP_TIME" ]; then
sleep $ES_STARTUP_SLEEP_TIME
fi
if ! ps -p $pid > /dev/null ; then
exit 1
fi
exit 0
fi
exit $?
下面是自己写的一个脚本(屏蔽了敏感信息):
对是否用main函数的问题,有利有弊
#!/usr/bin/env bash
# 用于某接口数据批量回溯
# @author liyangyang
# @time 2019/9/5
init(){
JAVA_HOME=/opt/soft/jdk1.8
# `dirname $0` 用于取得脚本文件所在目录
path=$(cd `dirname $0` && pwd)
data_path=$1
}
build_classpath(){
# 修改环境变量,只在当前运行该脚本当前运行的shell起效,对子shell或者父shell都无效
printf "JAVA_HOME = ${JAVA_HOME} \n"
CLASSPATH=${path}/test_work.jar:${path}/lib:${JAVA_HOME}/lib/rt.jar # :${JAVA_HOME}/lib/dt.jar
for jar in `ls ${path}/lib`; do
CLASSPATH="${CLASSPATH}":"${path}/lib/${jar}"
done
printf "CLASS_PATH = ${CLASSPATH} \n"
}
main(){
init
printf "开始执行文件:$0,源数据文件路径:${data_path} \n"
build_classpath
date +"%Y-%m-%d %H:%M" >>${path}/logs/test_process.log 2>&1
printf "开始进行数据回溯~ \n" >> ${path}/logs/test_process.log 2>&1
start_time=$(date +"%s")
/opt/soft/jdk1.8/bin/java -Xms5000M -Xmx5000M -Xmn256M -XX:+UseParallelGC -XX:+UseParallelOldGC -classpath ${CLASSPATH} com.yoylee.data.findData
end_time=$(date +"%s")
printf "数据回溯结束,花费时间 $((end_time-start_time))s" >> ${path}/logs/test_process.log 2>&1
# 退出脚本
exit 0;
}
if (( $# != 1 )) ; then
printf "请输入正确参数!参数:需要处理的文件全路径 \n";
exit 1;
fi
# 开始执行
main