常用的也是最容易忘记的Shell知识

最近在写个Shell脚本,由于是第二次写比较大的脚本,第一次是几年前的事情。shell的相关知识已经忘记得差不多了。不过大概知道解决问题的思路,剩下的就是用shell脚本的语法来实现。因此,没有系统地学习shell,基本上是想要实现个什么功能就到网上去找。本文简单罗列了我曾经搜索过的问题(不全),一是方便自己以后查找,二来可能对其他人有些帮助。因为我只关注结果,所以有些问题有更好的解决方法,抛砖引玉。如果有错误的地方,欢迎指正!


Q: 如何去掉前缀?

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. path="target/word.doc"  
  2. echo ${path#*/}  

输出结果将是word.doc


Q: 如何去掉tar.gz后缀得到文件名

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. export file=foo.tar.gz  
  2. echo ${file%.tar.gz}  

Q: 如何将字符串转为大写或小写

A: 

bash3.x

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. $ echo $VAR_NAME | tr '[:upper:]' '[:lower:]'  
  2. $ echo $VAR_NAME | tr '[:lower:]' '[:upper:]'  

bash4.x

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. x="HELLO"  
  2. echo $x  # HELLO  
  3.   
  4. y=${x,,}  
  5. echo $y  # hello  
  6.   
  7. z=${y^^}   
  8. echo $z  # HELLO  
如果只用一个,或者^, 就只会把首字母变成小写或大写。


Q: 如何简化if else?

初学者在shell中可能会写很多ifelse,从而导致代码冗长。比如,

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. somecommand  
  2. if [ $? -ne 0 ]; then  
  3.   echo "command failed."  
  4.   exit 1  
  5. fi  
简化的一个方法是,首先写一个die函数:
[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. function die  
  2. {  
  3.   echo $@  
  4.   exit 1  
  5. }  
然后,上面的if就可以改写为

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. somecommand || die "Command failed."  

类似的,如果要检查某个变量var有没有设置,如果没有设置就退出,可以写成:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. [ -z $var ] && die "environment variable var is not set"  

Q: 如何判断某个字符串是不是以特定字符串开始,类似于Java中的startWith方法?

A: 看个例子

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. var="# this is comments"  
  2. [[ $var == "#"* ]] && echo "line starts with # is ignored."  

类似的就可以实现endWith了。


Q: 如何以空格作为分割符,看一个字符串被分成几段?

A:比如我想知道“first second third”被空格分成几段,该例子是3。方法之一是:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. echo "first second third | "awk -F' ' '{ print NF }'  


Q:另外一个处理命令行参数的方法

A: 之前介绍用getopts来处理输入参数,最近发现另外一种方法,也是特别方便的。原理就是每次处理一个参数,然后执行一次shift,这样下一个参数就变成$1了。

比如命令:ls -l -R, 其中: $1=-l, $2=-R。执行shift 1以后,$1=-R

这种方式处理参数的模板如下:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. while [ $# -gt 0 ]; do  
  2.  case $1 in  
  3.   '--option_a' )  
  4.     # TODO 处理--option_a相关的参数  
  5.   
  6.     # 1)如果--option_a是一个布尔型的参数,那么,直接设置相关的变量,然后shift 1以便处理后面的参数  
  7.     # 2)如果--option_a后面需要跟上一个输入值,那么$2就是相应的输入值,拿到$2后shift 2以便处理后面的参数。  
  8.   '--enable_logging')  
  9.     enable_logging=1  
  10.     shift 1;; #注意:这里必须是两个分号  
  11.   ‘--file’)  
  12.      file=$2;  
  13.      shift 2;;  
  14.    'help' | '-h' | '--help')  
  15.     printHelpAndExit;;  
  16.   *)  
  17.     info "Invalid operation $1";  
  18.     printHelpAndExit;;  
  19.  esac  
  20. done  

=========================================================

Q: 如何取得当前脚本的名字? 

A: declare me=`basename $0` (注意,`是和~同一个键的那个字符)。这样在脚本中需要用到当前脚本名字的地方用$me来代替,比如usage信息的时候。


Q: 如何取得当前脚本所在的目录?

A: declare parent_dir=$(dirname $(readlink -f $0))


Q: 如何取得当前用户的用户名

A:  declare current_user=$(id -un)


Q: 如何取得当前用户的home目录

A: declare current_user_home=$(dirname ~)/`basename ~` 不知道为什么,脚本中直接用~来表示当前用户的home目录不行。所以只好用这种办法了。


Q: 如何定义常量?

A: readonly version="V1.0"


Q: 如何引用变量var?

A: 当然是$var了,不过如果想把var中的值和其他字符拼起来的话,比如$var_log,shell会吧var_log当成一个变量。这时可以用${var}_log


Q: Bash中支持指针吗?

A: 支持。

假设current_user=jewes,同时定义var=current_user,${!var}的值就是jewes。它先对var求值,然后将var的值作为一个变量再求值。


Q: 如何定义函数

A: 函数的定义方式如下。如果是在ksh中,括号可以不要。

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. function foo()  
  2. {  
  3.   # function body  
  4. }  

Q: 如何使用if-else?

A: bash中的if条件的格式是为:

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if[ condition ]; then  
  2.   # do something  
  3. else  
  4.   # do something else  
  5. fi  
注意:
  1. condition和[]间必须要有个空格。
  2. 如果是ksh,要写成if [[ condition ]], bash也支持这种写法。


Q: 函数可以返回一个字符串吗?

A: 不能直接return "string"。但可以用如下方法:

在函数内部用echo 来输出字符你想要返回的字符串,比如

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. function get_name()  
  2. {  
  3.   echo jewes  
  4. }  
然后在调用函数的地方写成$(get_name),这样就能得到函数内容输出的字符串。


Q: 如何给函数传递参数

A: 直接将参数写在函数名称的后面,就像执行shell命令一样,如果参数中有空格,需要将其用引号包起来。这个点很重要,如果没有引号,shell会认为是几个参数,比如"hello world"是一个参数;而hello world 是两个参数。


Q: 如何取得函数的参数?

A: $0表示第一个参数,以此类推。$#表示总共有几个参数传递进来。$*表示所有传递进来的参数。


Q: 如何定义局部变量?

A: local id=jewes,不过local只能在一个方法中定义。貌似ksh不支持local


Q: 如何取得函数的返回值

A: 用$?表示上一个函数或者shell命令的返回值。如果你的代码根据不同的返回值执行不同的代码,建议先将$?赋值给一个变量,比如:

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. func  
  2. if [ $? -eq 0 ]; then  
  3.   # do something  
  4.   echo $?   ####-->>> 如果你以后再这句话之前加了新的代码,$?就不再是func的返回值了,因此建议在func执行完后立刻保存其返回值到某个变量中。  
  5. else  
  6.   # do something else  
  7. fi  

Q: 如何判定一个文件是否存在?

A: Sample code

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if [ -f $filename ]; then  
  2.   # do something when file exists  
  3. else  
  4.   # do something when file doesn't exist  
  5. fi  

注意:

  1. []和里面的条件直接必须有空格。比如 [ -f $filename] 就不对了。
  2. 如果要看一个目录是否存在用-d
  3. 如果要判定一个文件不存在,在-f 前面加上!


Q: 如何判定一个目录是否为空?

A: Sample code

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if [[ "$(ls -A $WSPACE_ROOT/$wspacename)" ]]; then  
  2.   echo "folder not empty"  
  3. fi  

Q: 如何输出有颜色的字符

A: Sample code

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. function echo_green()  
  2. {  
  3.   COLOR='\033[01;32m'   # green color  
  4.   RESET='\033[00;00m'   # normal color  
  5.   MESSAGE=${@:-"${RESET}"}  
  6.   echo -ne "${COLOR}${MESSAGE}${RESET}"  
  7. }  
  8. cho_green "A green message"  
其中,32m表示绿色, 31m表示红色,34m表示蓝色,33m表示黄色。


Q: 如何提示输入密码并在屏幕上显示为*

A: Sample code

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. function enter_password()  
  2. {  
  3.   local prompt=$1 # prompt message  
  4.   unset user_typed_password  
  5.   while IFS= read -p "$prompt" -r -s -n 1 char  
  6.   do  
  7.     if [[ $char == $'\0' ]]; then  
  8.       break  
  9.     fi  
  10.     prompt='*'  
  11.     user_typed_password+="$char"  
  12.    done  
  13.   echo ""         # force a carriage return to be output  
  14. }  
  15.   
  16. enter_password "Tell me your bank account password:"  
其中,密码将被保存到user_typed_password,调用这个方法后就可以从其中取得用户输入的密码。


Q: 如何记录某台命令执行了多长时间?

A: Sample code

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. starttime=$(date +%s)  
  2. # some time consuming command  
  3. endtime=$(date +%s)  
  4. timediff=$(( $endtime - $starttime ))  
timediff中保存的是两个时间差,以秒为单位。


Q: 如何进行算术运算?

A: 举个例子

local index=1

index=$(( $index + 1 ))

+可以是+-x/%


Q: 如何在指定的列输入文件

A: 假如你要在屏幕上输出格式相对整齐的表格数据,希望在指定的列输出文字,可以用:

echo -ne "\033[65G"

echo "[ OK ]"

表示在第65列输出[ OK ]


Q: 如何生成公私钥?

A: ssh-keygen -qC "Some comments" -f <private key filename> -N ""

这个命令将产生一对密钥对并保存到指定的文件中,公钥以.pub文件结尾。这个命令不需要人工干预。因为在我的环境下,脚本要给用户自动生成密钥对。


Q: 在ssh到远程机器的时候,如何自动接受远程机器的HostKey?

A: 如果你第一次ssh到远程机器,ssh会提示你是否接收远程机器的Key。在脚本环境下,它可能会使你的脚本停下来。可以在ssh的时候指定下面两个参数:

 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null 第一个参数是让ssh自动接受远程机器的key,后面一个参数是不让其保存默认的knownhosts文件中,这主要是避免远程机器改变了key,脚本就又不工作了。


Q: 如果远程机器只支持telnet,如何自动登录到机器上执行命令

A: 用expect。


Q: 如何将一个函数放到后台运行?

A: 就像shell命令一样,在脚本中调用一个函数的时候只要在后面跟上 & ,shell将开启一个新的进程来执行这个函数,新的进程的id可以通过$!来取得。


Q: 如何将一些公共的函数放到一个文件中以便被多个脚本重用

A: 建个新文件比如common_func.sh,把常量和工具函数都放到里面。

在想要用这些公共函数的Shell脚本比如main.sh中,只需加上". ./common_func.sh"就可以了,前提是它们是在同一个目录里面。

上面的写法在当前目录下运行这个脚本是没有问题的,如果在别的目录下运行main.sh会遇到找不到common_func.sh这个文件。解决办法是在脚本里面先找到当前脚本的父目录,然后用绝对路径来引用common_func.sh这个文件(假设它们之间的相对位置的固定)。参加前面的FAQ如何找到当前脚本的父目录


Q: 有什么方法能方便地读取脚本的参数吗?

A: 用getopts。

举个例子:我们的脚本是myscript.sh, 支持一个带参数的选项-w,一个不带参数的选项-h。

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. while getopts hw: options 2> /dev/null  
  2. #注意w后面有个: 表示它后面必须要有一个参数。h后面没有冒号,表示它后面不能有参数。  
  3. do  
  4.   case $options in  
  5.     h) print_usage_and_exit;;  #必须要两个;;  
  6.     w) val=$OPTARG;;  #取得跟着-w后面的值  
  7.     \?) print_usage_and_exit;;  
  8.   esac  
  9. done  
注意这种方法的选项只能是一个字母。


Q: Shell有什么单元测试的框架吗?

A: shunit2,简单看了下觉得应该不错,不过没有用到我的脚本中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值