设为首页 收藏本站
查看: 730|回复: 0

Linux Shell 编程实战技巧

[复制链接]

尚未签到

发表于 2018-8-30 06:35:20 | 显示全部楼层 |阅读模式
  目前,越来越多的企业应用会部署在 Linux 系统上的,而 Linux Shell 脚本可以极大地帮助我们完成这些应用的运维任务。这使得 Linux Shell 开发技能成为开发人员的一项重要的、有竞争力的技能。本文就笔者的实际开发经验,以 Korn Shell 为例分享了脚本开发中的常见问题及相关技巧。
避免定时任务脚本的常见问题
  很多脚本在实际使用的时候往往是以定时任务的方式运行,而非手工运行。但是实现同样功能的脚本在这两种运行方式下可能遇到的问题不尽相同。
  以定时任务方式运行的脚本往往会遇到以下几个问题。

  •   路径问题:当前目录往往不是脚本文件所在目录。因此,脚本在引用其使用的外部文件,如配置文件和其它脚本文件时,无法方便得使用相对路径。
  •   命令找不到问题:脚本中使用到的一些外部命令,在手工执行脚本的时候可以正常调用。但是在定时任务下运行则可能出现脚本解析器找不到相关命令的问题。
  •   脚本重复运行问题:一次脚本的执行未结束,而下一次脚本的运行已经开始。导致系统中有多个进程在同时运行同一个脚本。
  下面分享定时任务脚本开发中上述几个常见问题的处理方法。
路径问题
  定时任务下当前路径往往不是脚本文件所在目录。因此我们需要用绝对路径来引用。即先获取脚本所在目录,然后以该目录为基础采用绝对路径的方式去引用脚本所需的外部文件。方法如下面代码所示。
清单 1. 获取脚本文件所在路径
#!/usr/bin/ksh  

  
echo "Current path is: `pwd`"
  
scriptPath=`dirname $0` #获取脚本所在路径
  

  
echo "The script is located at: $scriptPath"
  
cat "$scriptPath/readme" #使用绝对路径引用外部文件
  将清单 1 中的脚本置于目录/opt/demo/scripts/auto-task 下,并在 cron 中添加该脚本。定时任务运行输出如下。
  Current path is: /home/viscent
  The script is located at: /opt/demo/scripts/auto-task
命令找不到问题
  定时任务下运行的脚本可能出现脚本解析器找不到相关命令的问题。比如 Oracle 数据库中的 sqlplus 命令,脚本在调用该命令时若没有特殊处理则在定时任务下执行会使脚本解析器无法找到这个命令,出现如下所示的错误提示:
  sqlplus: command not found
  这是因为脚本在定时任务下执行时脚本是由非登录式 Shell 来执行的,并且执行脚本的父 Shell 并非 Oracle 用户的 Shell。因此,此时 Oracle 用户的.profile 文件并没有被调用。故解决的方法是在脚本的开头添加以下代码:
清单 2. 解决找不到外部命令问题
source /home/oracle/.profile  也就说,对于外部命令找不到的问题,可以通过在脚本的开头加一个 source 用户的.profile 文件的语句来解决。
脚本重复运行问题
  定时任务脚本的另外一个常见问题是脚本重复运行的问题。比如,一个脚本被设置为每 5 分钟运行一次。若某一次该脚本的运行无法在 5 分钟内结束的话,定时任务服务仍然会新启一个进程来执行该脚本。这时就出现了运行同一个脚本的多个进程。而这可能导致脚本功能紊乱。并且浪费了系统资源。 避免脚本重复运行的方法通常有两种。一是在脚本执行时先检查系统是否存在运行该脚本的其它进程。若存在,则终止当前脚本的运行。二是,脚本运行时检查系统中是否存在其它进程运行该脚本。若存在,则结束那个进程(此方法有一定风险,慎用!)。这两种方法均需要在脚本的开头检查系统是否已经存在运行当前脚本的进程,若存在这样的进程则获取该进程的 PID。示例代码如下清单 3 所示。
清单 3. 防止脚本重复运行方法 1
#!/usr/bin/ksh  

  
main(){
  
selfPID="$$"
  
scriptFile="$0"
  

  
typeset existingPid
  
existingPid=`getExistingPIDs $selfPID "$scriptFile"`
  

  
if [ ! -z "$existingPid" ]; then
  
  echo "The script already running, exiting..."
  
  exit -1
  
fi
  

  
doItsTask
  

  
}
  

  
#获取除本身进程以外其它运行当前脚本的进程的 PID
  
getExistingPIDs(){
  
selfPID="$1"
  
scriptFile="$2"
  

  
ps -ef | grep "/usr/bin/ksh ${scriptFile}" | grep -v "grep" | awk "{ if(\$2!=$selfPID) print \$2 }"
  
}
  

  
doItsTask(){
  
echo "Task is now being executed..."
  
sleep 20  #睡眠 20s,以模拟脚本在执行需要长时间完成的任务
  
}
  

  
main $*
清单 4. 防止脚本重复运行方法 2
#!/usr/bin/ksh  

  
main(){
  
selfPID="$$"
  
scriptFile="$0"
  

  
typeset existingPid
  
existingPid=`getExistingPIDs $selfPID "$scriptFile"`
  

  
if [ ! -z "$existingPid" ]; then
  
  echo "The script already running, killing it..."
  
  kill -9 "$existingPid" #此方法有一定风险,慎用!
  
fi
  

  
doItsTask
  

  
}
  

  
#获取除本身进程以外其它运行当前脚本的进程的 PID
  
getExistingPIDs(){
  
selfPID="$1"
  
scriptFile="$2"
  
ps -ef | grep "/usr/bin/ksh ${scriptFile}" | grep -v "grep" | awk "{ if(\$2!=$selfPID) print \$2 }"
  
}
  

  
doItsTask(){
  
echo "Task is now being executed..."
  
sleep 20  #睡眠 20s,以模拟脚本在执行需要长时间完成的任务
  
}
  

  
main $*
  回页首
脚本调试技巧
  虽然 Shell 开发的一个普遍问题是调试困难,缺乏有效的调试工具。但是,我们可以采取一些能够一定程度上帮助我们规避调试困难的开发与调试的方式。 由于是脚本开发,不少人习惯于从直接地一行行地写代码,一个脚本里面甚至于一个函数都没有。虽然这种方式在语法上和功能上并无问题。但这增加了调试的难度。相反,如果采用模块化的方式去编写脚本,则使代码结构清晰、便于调试。这点,可以看这样一个例子。
  假设下面的脚本的功能是收集生产环境中的相关日志文件,用于定位问题。需要收集的日志文件包括操作系统日志、中间件日志以及应用系统本身的日志。这些文件会被压缩成一个 gz 文件。
清单 5. 自动收集日志文件
#!/usr/bin/ksh  

  
main(){
  
collectSyslog #收集系统日志文件
  
collectMiddlewareLog #收集中间件日志文件
  
collectAppLog #收集应用系统日志文件
  
tar -zcf logs.tgz syslog.zip mdwlog.zip applog.zip #将三中类型的日志打包,方便下载
  
}
  若脚本执行报如下错误:
  tar: applog.zip: Cannot stat: No such file or directory
  我们可以很快锁定 collectAppLog 这个函数。因为它负责输出 applog.zip 这个文件。而没有必要看代码中的其它部分。
  采用模块化的方式的另一个好处是代码调试的结果可以巩固下来。比如上面的例子中,如果你已经调试好了操作状态日志收集的函数。接下来调试其它函数的时候,这些被调试的代码尽管可能需要改动。但是这些改动影响到之前已经调试好的代码的可能并不大。相反,若是一个脚本中通篇都是语句,而不带函数,则改动其中一行代码,收集三种日志的功能可能都受影响。
  另外一个典型的场景是脚本编写过程中,我们可能会因为不太确定一些问题如何处理而写一些尝试性的代码。然后,通过反复的调试去确认正确的处理方式。而事实上这些尝试性的代码可能就是一条语句甚至一个命令。但不少人是在大段的代码中反复去调试这一小段代码。这将非常耗时间。尤其是调试过程中代码中的其它部分调试时出现错误时,作者还得先解决其它错误,否则可能会时我们真正要调试的代码无法被执行到。这种情形下,专门写一个测试性的小脚本。

  在该脚本中调试还我们不太确定该如何写的代码,如何将其”集成”到我们正在开发的脚本中。这样可以提高调试效率,避免消耗本不该消耗的时间。比方说,我们在编写过程中需要获取脚本本身所在进程的进程>  回页首
处理大段字符输出
  脚本开发中经常要处理的一个问题是输出提示信息。当然,对于简短的提示信息输出,使用 echo 命令就足够了。但是,对于大段的提示信息输出仍然使用 echo 命令处理则显得不够优雅。一种更适合的方法是使用 cat 命令结合输入重定向。下面通过一个具体例子来说明这点。
  假设下面的脚本会将某个程序安装到用户指定的目录下。若用户指定的目录不存在,则提示
  用户检查指定的目录是否正确,并重新执行脚本。
清单 6. 使用 echo 命令输出大段字符
#!/usr/bin/ksh  

  
path="$1"
  

  
if [ ! -d "$path" ]; then
  
        #这里还必需处理星号这个特殊字符的显示
  
echo '****************************************************'
  
echo ERROR
  
echo "The destination directory not exists,make sure below directory you specified is correct:"
  
echo ${path}
  
echo "Then re-run this script."
  
echo '****************************************************'
  
fi
  这种方式的代码可读性不是很好,阅读者需要阅读多个 echo 命令然后再进行"综合"才能准确理解提示信息是什么。另外,一旦提示信息需要改动。这种改动可能因为改动其中一个 echo 命令时不小心多了一个双引号等特殊字符而引起语法错误,从而影响了整个脚本的执行。
  清单 7 的代码则展示了如何使用 cat 命令和输入重定向来更好地处理大段文本的输出。
清单 7. 使用 cat 命令输出大段字符
#!/usr/bin/ksh  

  
path="$1"
  
if [ ! -d "$path" ]; then
  
cat> $fileName
  

  
fi
  

  
done > $fileName
  

  
fi
  

  
done



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-558577-1-1.html 上篇帖子: 怎么在文件中每行后面添加行数号(shell命令) 下篇帖子: shell 脚本练习4-MonDeoLove
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表