十三个写好shell脚本的技巧分享

简介:

十三个写好shell脚本的技巧分享

更新时间:2018年06月19日 08:50:14 作者:无明 我要评论
这篇文章主要给大家介绍了关于写好shell脚本的13个技巧的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

产品的最终用户通常不懂技术,所以不管你怎么折腾产品代码都无所谓。但脚本代码不一样,它们是开发人员写给开发人员的。

有多少次,你运行./script.sh,然后输出一些东西,但却不知道它刚刚都做了些什么。这是一种很糟糕的脚本用户体验。我将在这篇文章中介绍如何写出具有良好开发者体验的 shell 脚本。

产品的最终用户通常不懂技术,所以不管你怎么折腾产品代码都无所谓。但脚本代码不一样,它们是开发人员写给开发人员的。

这样会导致一些问题:

混乱的脚本——我知道,我们都是工程师,读得懂代码,但即使这样,也请为我们这些对 Shell 脚本不是很熟练的人考虑一下(我们在写代码时也会为你们考虑的)。
满屏的日志和错误输出——就算我们也是工程师,并不代表我们了解你所做的一切。
弄得一团糟却没有做好清理工作——是的,我们可以顺着你的脚本手动撤销变更,但你真的会让那些信任你的脚本的人这么做吗?

所以,我们可以通过一些方法来为自己和别人写出更好的 shell 脚本。这里给出的所有示例都可以使用与 POSIX 标准兼容的 shell 运行(#!/bin/sh),因为它是最常用的。嫌文章太长了可以只看以下总结部分:

提供--help标记
检查所有命令的可用性
独立于当前工作目录
如何读取输入:环境变量 vs. 标记
打印对系统执行的所有操作
如果有必要,提供--silent选项
重新开启显示
用动画的方式显示进度
用颜色编码输出
出现错误立即退出脚本
自己执行清理工作
在退出时使用不同的错误码
在结束时打印一个新行

有时间的话可以接着往下看具体内容:

提供--help标记

安装在系统上的二进制文件通常带有man帮助文档,但对于脚本来说就不一定了。因此我们通常需要为脚本提供-h或--help标记来打印有关如何使用脚本的信息。如果其他工程师需要修改脚本,这也可以作为脚本的内联文档:
?
1
2
3
4
5

!/bin/sh

if [ ${#@} -ne 0 ] && [ "${@#"--help"}" = "" ]; then
printf -- '...help...n';
exit 0;
fi;

这段脚本先计算参数长度(${#@} -ne 0),只有当参数长度不为零时才会检查--help标记。下一个条件会检查参数中是否存在字符串“--help” 。第一个条件是必需的,如果参数长度为零则不需要打印帮助信息。

检查所有命令的可用性

脚本通常会调用其他脚本或二进制文件。在调用可能不存在的命令时,请先检查它们是否可用。可以使用“command -v 二进制文件名称”来执行此操作,看看它的退出代码是否为零。如果命令不可用,可以告诉用户应该如何获得这个二进制文件:
?
1
2
3
4
5
6
7
8
9

!/bin/sh

_=$(command -v docker);
if [ "$?" != "0" ]; then
printf -- 'You don't seem to have Docker installed.n';
printf -- 'Get it: https://www.docker.com/community-editionn';
printf -- 'Exiting with code 127...n';
exit 127;
fi;

...

独立于当前工作目录

从不同的目录执行脚本可能会发生错误,这样的脚本没有人会喜欢。要解决这个问题,请使用绝对路径(/path/to/something)和脚本的相对路径(如下所示)。

可以使用dirname $0引用脚本的当前路径:
?
1
2
3
4

!/bin/sh

CURR_DIR="$(dirname $0);"
printf -- 'moving application to /opt/app.jar';
mv "${CURR_DIR}/application.jar" /opt/app.jar;

如何读取输入:环境变量 vs. 标记

脚本通过两种方式接受输入:环境变量和选项标记(参数)。根据经验,对于不影响脚本行为的值,可以使用环境变量,而对于可能触发脚本不同流程的值,可以使用脚本参数。

不影响脚本行为的变量可能是访问令牌和 ID 之类的东西:
?
1
2
3
4
5
6

!/bin/sh

do this

export AWS_ACCESS_TOKEN='xxxxxxxxxxxx';
./provision-everything

and not

./provisiong-everything --token 'xxxxxxxxxxx';

影响脚本行为的变量可能是需要运行实例的数量、是异步还是同步运行、是否在后台运行等参数:
?
1
2
3
4
5

!/bin/sh

do this

./provision-everything --async --instance-count 400

and not

INSTANCE_COUNT=400 ASYNC=true ./provision-everything

打印对系统执行的所有操作

脚本通常会对系统执行有状态的更改。不过,由于我们不知道用户何时会向发送SIGINT,也不知道脚本错误何时可能导致脚本意外终止,因此很有必要将正在做的事情打印在终端上,这样用户就可以在不去查看脚本的情况下回溯这些步骤:
?
1
2
3
4
5
6
7

!/bin/sh

printf -- 'Downloading required document to ./downloaded... ';
wget -o ./downloaded https://some.site.com/downloaded;
printf -- 'Moving ./downloaded to /opt/downloaded...';
mv ./downloaded /opt/;
printf -- 'Creating symlink to /opt/downloaded...';
ln -s /opt/downloaded /usr/bin/downloaded;

在必要时提供--silent选项

有些脚本是为了将其输出传给其他脚本。虽说脚本都应该能够单独运行,不过有时候也有必要让它们把输出结果传给另一个脚本。可以利用stty -echo来实现--silent标记:
?
1
2
3
4
5
6
7
8
9
10
11
12

!/bin/sh

if [ ${#@} -ne 0 ] && [ "${@#"--silent"}" = "" ]; then
stty -echo;
fi;

...

before point of intended output:

stty +echo && printf -- 'intended outputn';

silence it again till end of script

stty -echo;

...

stty +echo;
exit 0;

重新开启显示

在使用stty -echo关闭脚本显示之后,如果发生致命错误,脚本将终止,而且不会恢复终端输出,这样对用户来说是没有意义的。可以使用trap来捕捉SIGINT和其他操作系统级别的信号,然后使用stty echo打开终端显示:
?
1
2
3
4
5
6
7
8
9
10
11
12

!/bin/sh

error_handle() {
stty echo;
}
if [ ${#@} -ne 0 ] && [ "${@#"--silent"}" = "" ]; then
stty -echo;
trap error_handle INT;
trap error_handle TERM;
trap error_handle KILL;
trap error_handle EXIT;
fi;

...

用动画的方式显示进度

有些命令需要运行很长时间,并非所有脚本都提供了进度条。在用户等待异步任务完成时,可以通过一些方式告诉他们脚本仍在运行。比如在while循环中打印一些信息:
?
1
2
3
4
5
6
7
8
9
10
11

!/bin/sh

printf -- 'Performing asynchronous action..';
./trigger-action;
DONE=0;
while [ $DONE -eq 0 ]; do
./async-checker;
if [ "$?" = "0" ]; then DONE=1; fi;
printf -- '.';
sleep 1;
done;
printf -- ' DONE!n';

或者可以做一些更好玩的小玩意儿,比如 http://mywiki.wooledge.org/BashFAQ/034

用颜色编码输出

在脚本中调用其他二进制文件或脚本时,对它们的输出进行颜色编码,这样就可以知道哪个输出来自哪个脚本或二进制文件。这样我们就不需要在满屏的黑白输出文本中查找想要的输出结果。

理想情况下,脚本应该输出白色(默认的,前台进程),子进程应该使用灰色(通常不需要,除非出现错误),使用绿色表示成功,红色表示失败,黄色表示警告。
?
1
2
3
4
5
6

!/bin/sh

printf -- 'doing something... n';
printf -- '033[37m someone else's output 033[0mn';
printf -- '033[32m SUCCESS: yay 033[0mn';
printf -- '033[33m WARNING: hmm 033[0mn';
printf -- '033[31m ERROR: fubar 033[0mn';

可以使用033[Xm,其中X代表颜色代码。有些脚本使用e而不是033,但要注意e不适用于所有的 UNIX 系统。

正确示范

可在.sh 中使用的所有颜色和修饰符 https://misc.flogisoft.com/bash/tip_colors_and_formatting

出现错误立即退出脚本

set -e表示从当前位置开始,如果出现任何错误都将触发EXIT。相反,set +e表示不管出现任何错误继续执行脚本。

如果脚本是有状态的(每个后续步骤都依赖前一个步骤),那么请使用set -e,在脚本出现错误时立即退出脚本。如果要求所有命令都要执行完(很少会这样),那么就使用set +e。
?
1
2
3
4
5
6
7
8
9

!/bin/sh

set +e;
./script-1;
./script-2; # does not depend on ./script-1
./script-3; # does not depend on ./script-2
set -e;
./script-4;
./script-5; # depends on success of ./script-4

...

自己执行清理工作

大多数脚本在出现错误时不会执行清理工作,能够做好这方面工作的脚本实属罕见,但这样做其实很有用,还可以省下不少时间。前面已经给出过示例,让stty恢复正常,并借助trap命令来执行清理工作:
?
1
2
3
4
5
6
7
8
9
10

!/bin/sh

handle_exit_code() {
ERROR_CODE="$?";
printf -- "an error occurred. cleaning up now... ";
# ... cleanup code ...
printf -- "DONE.nExiting with error code ${ERROR_CODE}.n";
exit ${ERROR_CODE};
}
trap "handle_exit_code" EXIT;

... actual script...

在退出时使用不同的错误码

在绝大多数 shell 脚本中,exit 0 表示执行成功,exit 1 表示发生错误。对错误与错误码进行一对一的映射,这样有助于脚本调试。
?
1
2
3
4
5
6
7
8
9
10
11

!/bin/sh

...

if [ "$?" != "0" ]; then
printf -- 'X happened. Exiting with status code 1.n';
exit 1;
fi;

...

if [ "$?" != "0" ]; then
printf -- 'Y happened. Exiting with status code 2.n';
exit 2;
fi;

这样做有另一个额外的好处,就是其他脚本在调用你的脚本时,可以根据错误码来判断发生了什么错误。

在结束时打印一个新行

如果你有在遵循脚本的最佳实践,那么可能会使用printf代替echo(它在不同系统中的行为有所差别)。问题是printf在命令结束后不会自动添加一个新行,导致控制台看起来是这样的:

看起来是多么的平淡

这样一点也不酷,可以通过简单的方式打印一个新行:
?
1
2
3
4

!/bin/sh

... your awesome script ...

printf -- 'n';
exit 0;

这样就可以得到:

好多了哈

别人会感谢你这么做的。

总结

这篇文章大致总结了一些简单易用的技巧,让 shell 脚本更易于调试和使用。

原文链接:https://codeburst.io/13-tips-tricks-for-writing-shell-scripts-with-awesome-ux-19a525ae05ae

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。
您可能感兴趣的文章:

Spark-shell批量命令执行脚本的方法

原文链接:https://mp.weixin.qq.com/s/f3xDHZ7dCQr7sHJ9KDvuyQ

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

shell
脚本
技巧

相关文章

Linux查找占用的端口,并杀死进程的方法

Linux查找占用的端口,并杀死进程的方法
下面小编就为大家带来一篇Linux查找占用的端口,并杀死进程的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
2017-05-05
列出所有Bash Shell内置命令的方法示例

列出所有Bash Shell内置命令的方法示例
大家都知道不同的Shell内置命令有所不同,所以下面这篇文章主要给大家介绍了关于如何列出所有Bash Shell内置命令的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
2018-02-02
linux下非root用户运行tomcat

linux下非root用户运行tomcat
这篇文章主要介绍了linux下非root用户运行tomcat ,需要的朋友可以参考下
2017-12-12
Shell脚本实现的单机流量统计功能

Shell脚本实现的单机流量统计功能
这篇文章主要介绍了Shell脚本实现的单机流量统计功能,本文给出实现代码和使用方法,及运行结果,需要的朋友可以参考下
2014-11-11
备份网站内容的shell脚本代码

备份网站内容的shell脚本代码
备份网站内容的shell脚本代码,需要的朋友可以参考下
2013-02-02
linux命令之find命令简单概述

linux命令之find命令简单概述
这篇文章主要为大家详细介绍了linux命令之find命令的简单使用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
2017-02-02
强制删除rpm包的方法

强制删除rpm包的方法
本文给大家介绍强制删除rpm包,对rpm包删除方法感兴趣的朋友可以一起学习
2015-11-11
Shell脚本读取标准ini配置文件Demo

Shell脚本读取标准ini配置文件Demo
这篇文章主要介绍了Shell脚本读取标准ini配置文件Demo,本文直接给出代码实例,需要的朋友可以参考下
2015-05-05
十三个写好shell脚本的技巧分享

十三个写好shell脚本的技巧分享
这篇文章主要给大家介绍了关于写好shell脚本的13个技巧的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2018-06-06
Linux服务器硬件运行状态及故障邮件提醒的监控脚本分享

Linux服务器硬件运行状态及故障邮件提醒的监控脚本分享
这篇文章主要介绍了Linux服务器硬件运行状态及故障邮件提醒的监控脚本分享,能够测试CPU和内存的占用状况以及HTTP端的异常,需要的朋友可以参考下
2016-03-03
相关文章
|
1月前
|
Shell
一个用于添加/删除定时任务的shell脚本
一个用于添加/删除定时任务的shell脚本
82 1
|
24天前
|
Shell Linux 测试技术
6种方法打造出色的Shell脚本
6种方法打造出色的Shell脚本
47 2
6种方法打造出色的Shell脚本
|
10天前
|
XML JSON 监控
Shell脚本要点和难点以及具体应用和优缺点介绍
Shell脚本在系统管理和自动化任务中扮演着重要角色。尽管存在调试困难、可读性差等问题,但其简洁高效、易于学习和强大的功能使其在许多场景中不可或缺。通过掌握Shell脚本的基本语法、常用命令和函数,并了解其优缺点,开发者可以编写出高效的脚本来完成各种任务,提高工作效率。希望本文能为您在Shell脚本编写和应用中提供有价值的参考和指导。
31 1
|
15天前
|
Ubuntu Shell 开发工具
ubuntu/debian shell 脚本自动配置 gitea git 仓库
这是一个自动配置 Gitea Git 仓库的 Shell 脚本,支持 Ubuntu 20+ 和 Debian 12+ 系统。脚本会创建必要的目录、下载并安装 Gitea,创建 Gitea 用户和服务,确保 Gitea 在系统启动时自动运行。用户可以选择从官方或小绿叶技术博客下载安装包。
37 2
|
28天前
|
监控 网络协议 Shell
ip和ip网段攻击拦截系统-绿叶结界防火墙系统shell脚本
这是一个名为“小绿叶技术博客扫段攻击拦截系统”的Bash脚本,用于监控和拦截TCP攻击。通过抓取网络数据包监控可疑IP,并利用iptables和firewalld防火墙规则对这些IP进行拦截。同时,该系统能够查询数据库中的白名单,确保合法IP不受影响。此外,它还具备日志记录功能,以便于后续分析和审计。
47 6
|
25天前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
1月前
|
监控 Unix Shell
shell脚本编程学习
【10月更文挑战第1天】shell脚本编程
67 12
|
1月前
|
存储 运维 监控
自动化运维:使用Shell脚本简化日常任务
【9月更文挑战第35天】在IT运维的日常工作中,重复性的任务往往消耗大量的时间。本文将介绍如何通过编写简单的Shell脚本来自动化这些日常任务,从而提升效率。我们将一起探索Shell脚本的基础语法,并通过实际案例展示如何应用这些知识来创建有用的自动化工具。无论你是新手还是有一定经验的运维人员,这篇文章都会为你提供新的视角和技巧,让你的工作更加轻松。
58 2
|
2月前
|
Shell
shell脚本变量 $name ${name}啥区别
shell脚本变量 $name ${name}啥区别
|
2月前
|
人工智能 监控 Shell
常用的 55 个 Linux Shell 脚本(包括基础案例、文件操作、实用工具、图形化、sed、gawk)
这篇文章提供了55个常用的Linux Shell脚本实例,涵盖基础案例、文件操作、实用工具、图形化界面及sed、gawk的使用。
535 2