java调用shell获取错误信息_shell编程的若干实用技巧

05c28feb20fe68e61c0a9a148da4cf88.png

不少人感兴趣, 想到会继续补充.

也可在评论区留言, 笔者选择比较典型的做补充.

=====

笔者工作中, 发现很多实用的shell编程技巧, 使用极少. 所以, 略作整理.

很多熟悉python的朋友, 本来在用shell就可以搞定问题的地方, 意外地使用python. 有一种说法, shell容易出错. 其实shell也可以写得很健壮, 健壮在于人而不在语言.

skill#0: 不可逆操作引用环境变量时用${var:?"undefined 'var'"}

比如:

rm -fr ${dir}/ # 如果dir未定义, 则删除根目录.
rm -fr ${dir:?"undefined 'dir'"} # 如果dir未定义, 报错. 

skill#1: 脚本出错即停使用 set -e -o pipefail

脚本开头第一句话, 写:
#!/bin/bash
set -e -o pipefail

如果出现单行或者单行管道命令出现错误, 脚本会停止执行并且报错.

skill#2: 获取basedir并且发起一个非util命令时使用绝对路径

script=$(basename ${BASH_SOURCE:-$0})
basedir=$(cd $(readlink -f $(dirname ${BASH_SOURCE:-$0}));pwd)
${basedir}/bin/execuable

用绝对路径的好处是用命令ps -C execuable可直接获得可执行程序的绝对路径. 不然的话, 需要查看/proc/${pid}/cwd 确定路径, 比较麻烦. 所以, 能省事就省事.

skill#3: 检查后台进程是否成功启动

set -e -o pipefail
sleep 1000 &
pid=$!
kill -0 ${pid} # kill -0 检查进程是否存活

先要获取后台进程的pid, 然后用kill -0检查是否存活.

skill#4: 优雅退出时清理用trap

mkdir /tmp/some.dir

finally(){
   local last_status=$? #最后一条命令的执行结果
   trap "" EXIT #避免执行finally嵌套调用死循环
   rm -fr /tmp/some.dir #清理工作
   exit ${last_status} #真正退出
}

trap finally EXIT

...

用trap给EXIT事件注册一个处理函数finally, 进入finally首先要获取最后一条命令的执行结果, 然后重置EXIT事件的处理函数, 否则可能会发生嵌套调用死循环.

注意: normal/abnormal exit都会调用EXIT的处理函数, 此不同于on_exit.

skill#5: 参数传递使用shift

script=$(basename ${BASH_SOURCE:-$0})
usage="FORMAT: ${script} <srcdir> <dstdir>"
srcdir=${1:?"undefined 'srcdir', $usage"};shift
dstdir=${1:?"undefined 'dstdir', $usage"};shift

skill#6: 通配符中含有$((表达式))时使用eval

num=100
for i in $(eval "echo {0..$(($num-1))}");do echo $i; done  

用eval可以构造特别复杂的序列

skill#7: eval可建立soft reference, 动态修改referenced对象

name=10
name_ref=name
echo $(eval "echo $$name_ref")

eval类似perl/python的eval, 能够在运行时, 把一段字符串, 当成代码执行, 并且可以读取和修改当前环境中绑定的变量. 具有元编程的能力, 可用于构造比较复杂的代码.

skill#8: 使用perl正则

shell不够, perl来凑; perl的sysadmin功能远强于python. 推荐使用perl.

perl正则简约统一, onelinar足以替换sed/awk/grep.

推荐perl的另外一个原因是, perl提供了非常丰富的括号类型, 便于写脚本.

sed/awk/grep的正则, 属于不同的dialect, 需要查看或者记忆三套规则, 容易混淆. 复杂的正则表达式, 拼正确往往需要化费一定时间. 简单场景, 比如搜索一个完整的单词, 用sed/awk/grep无妨, 复杂的正则表示, 建议用perl re.

sed和perl的对应关系

# E0表示最后一行
sed '1,$!d' dat.txt
perl -lne 'print if /1..E0/'

# re addr range
sed '/begin/,/end/!d' dat.txt
perl -lne 'print if /begin/../end/'

# substitution
sed '/begin/,/end/s/foobar/Foobar/g' dat.txt
perl -lpe 's/foobar/Foobar/g if/begin/../end/' dat.txt

# in-place substitution
 
sed -i '/begin/,/end/s/foobar/Foobar/g' dat.txt
perl -i -lpe 's/foobar/Foobar/g if/begin/../end/' dat.txt

awk和perl的对应关系

awk '{print $1}' dat.txt
perl -aF -lne 'print $F[0]' dat.txt

awk -F: '{print $1}' dat.txt
perl -aF: -lne 'print $F[0]' dat.txt

perl -aF'[;,s]+'  'print $F[0]' dat.txt #用正则/[;,s]+/分割字符串

grep和perl的对应关系

grep word dat.txt
perl -lne 'print if /word/' dat.txt

grep -Rin foobar *
find -type f |xargs -i perl -lne 'print if /foobar/i' '{}'

perl的其他举例

提取email地址
perl -lne 'print $1 if /b(S+@w+(.w+)*)/' foobar.html

批量修改文件名
find -type f |perl -lne 'chomp;rename $_=>"$_.bak"'
find -name "*.bak" |perl -lne 'chomp;rename $_=>$1 if /^(.*).bak$/'

批量替换字符串
find -name "*.cpp" -type f |xargs -i perl -i.bak -lpe 's/b0xdeadbeefb/0XDEADBEEF/g' '{}'    

skill#9: 获取含特定关键字的java进程pid的数组

# DataNode相关java进程pid存入positional variables
set -- $(ps h -C java -o pid,cmd | perl -ne 'print $1 if /^s+(d+).*DataNode/')
for p in $*;do
    echo $p
done

用positional variables捕获数组, 使用$* $@ $# $n shift操作数组, 比较方便.

虽然declare -a也可定义数组, 但难以记忆, 容易出错.

skill#10: 统计日志中某些词出现的频率

# 假设日志中包含包含"2018-10-08 12:00:00.345 [INFO/WARN/FATAL]..."信息, 统计INFO, WARN, FATAL的出现次数.
perl -lne '$h{$1}++ if /^d{4}-d{2}-d{2}s+d{2}:d{2}:d{2}.d{3}s+[b(w+)b/}{print join "n", map{"$_:$h{$_}"} keys %h' log

skill#11: here doc

不允许{backslash, $variable, $(cmd), $((expr))} interpolation, 边界词DONE用单引号

cat <<'DONE'
....
DONE

允许{backslash, $variable, $(cmd), $((expr))} interpolation, 边界词DONE用双引号或者裸词.

cat <<"DONE"
....
DONE

cat <<DONE
....
DONE

trim每一行前置的空白符用<<-

cat <<-DONE
  one
     two
       three
DONE

skill#12: 写函数其实很方便

return 0/1表示执行成功/失败, 函数用标准输出流返回结果, 使用$()提取返回值.

函数 abs_path

# 函数 abs_path
abs_path(){ #函数名字为abs_path
  usage="abs_path <path>"
  # 参数传递用positional variables
  local p=${1:?"undefined 'path': $usage"};shift #用local避免污染全局环境变量
  if [ -f $p ];then
    p=$(cd $(dirname $p);pwd)/$(basename $p)
  elif [ -d $p ];then
    p=$(cd $p;pwd)
  else
    # 错误返回1, 输出到标准错误流
    echo "error: '$p' is missing or is not a file/directory" >&2
    return 1
  fi
  # 成功返回0, 输出掉标准输出流
  echo $p 
  return 0
}

# 调用函数abs_path, 返回结果保存在cwd中.
cwd=$(abs_path .)

过程 add_bridge和del_bridge

add_bridge(){
  local usage="add_bridge <bridge-name> <subnet>"
  local bridge=${1:?"undefined <bridge-name>: $usage"};shift
  local subnet=${1:?"undefined <subnet>: $usage"};shift

  del_bridge $bridge
  ip link add $bridge type bridge
  ip link set dev $bridge up
  return 0
}

del_bridge(){
  local usage="del_bridge <bridge-name>"
  local bridge=${1:?"undefined <bridge-name>:$usage"};shift

  if ip link list | grep "<$bridge>" >/dev/null 2>&1;then
    ip link set dev $bridge down
    ip link delete dev $bridge
  fi  
  return 0
}

使用source或者.将函数所在的脚本文件include到主脚本中.

# assume that funtions.sh contains all your util funcitons
source funtions.sh
. funtions.sh  

skill#13: 死循环用colon(:)

while : ;do
  t=$(($RANDOM%10+1));
  echo sleep $t secs; 
  sleep $t;
done

skill#14: 字符串比较

# 判断字符串是否为空
[ -z $s ] #错误
[ -z "$s" ] #正确

# 判断字符串是否为不空
[ -n $s ] #错误
[ -n "$s" ] #正确

# 判断字符串是否相等
[ $s = "OK" ] #错误
[ x$sx = "xOKx" ] #错误
[ "x${s}x = "xOKx" ] #正确

skill#15: 判断字符串是否为合法IP地址

ip="192.168.1.1"
ill_formed=$(echo $ip|perl -lne 'print "ill-formed" unless /^d{1,3}(.d{1,3}){3}$/'
if [ -z "${ill_formed}" ];then
  echo "match"
else
  echo "not match"
fi

skill#16: 判对一组token是否包含某一个词

#!/bin/bash

contains(){
  local usage="Usage: contains <w> <elm0> <elm1> ..."
  local w=${1:?"undefined 'w', ${usage} "};shift
  if [ "$#" -eq 0 ];then 
    echo "Error: missing arguments, ${usage}" >&2
    return 1
  fi  
  perl -e "@h{qw/$*/}=(1)x$#;print $h{qq/$w/}"
  return 0
}

contains $*

skill#17: 如果Shell嵌入Perl无法解决问题, 那么就用Perl嵌入Shell

#!/usr/bin/perl
use strict;
use warnings;
...
my stdout=`shell_cmd` or die "$!"; # backticks enclose shell cmd.
...
my stdout=qx(shell_cmd) or die "$!"; # qx enclose shell cmd

skill#18: 打印Linux系统调用的标准errno和errmsg

perl -le 'foreach(0..133){$!=$_;print "$_:$!"}'

skill#19: 不用docker在本地搭建分布式系统

# 创建网桥
ip link add ${bridge} type bridge
ip link set dev ${bridge} up

# 创建一条ethernet网线
ip link add ${eth} type veth peer name ${br_eth}

# 把网线的一头接到网桥上
ip link set dev ${br_eth} master ${bridge}
ip link set dev ${br_eth} up

# 创建网络命名空间
ip netns add $netns

# 把网线的另外一头接到新创建的网络命名空间上.
ip link set ${eth} netns ${netns}

# 设置命名空间中以太网卡的网络地址
ip netns exec ${netns} ip link set dev ${eth} name "eth0"
ip netns exec ${netns} ifconfig "eth0" ${ip} netmask 255.255.255.0 up
ip netns exec $netns ifconfig "lo" up

# 如此往复可以创建多条连接在同一个网桥上的网线, 网线的另外一头处于不同的网络命名空间.

# 创建转发规则(Ubuntu 16.04, Manjaro可用)
iptables -t nat -A POSTROUTING -s ${subnet}.0/24 ! -o ${bridge} -j MASQUERADE
systemctl restart iptables
 
# 用nc或者python -m SimpleHTTPServer测试网络: 略

# 启动脚本start.sh, 使用独立的UTS, Mount命名空间.
unshare -u -m bash -x ./start.sh 

# 修改hostname
hostname ${hostname}

# 挂载目录
mount -B ${dir} ${mount_point} # 挂载目录

# 启动脚本start_server.sh, 使用独立的网络命名空间.
# 测试脚本中启动的服务, 拥有独立的UTS, mount和network命名空间.
ip netns exec ${netns} ./start_server.sh 

skill#20: 解决ssh远程执行nohup命令hang住问题

如果不关闭nohup的标准{输入, 输出, 错误}文件, ssh远程执行nohup命令会hang住.

ssh localhost "nohup python -m http.server 2>&1  &" #hang

使用 exec fd <&- 关闭文件fd

ssh localhost "exec nohup python -m http.server 2<&- 1<&- 0<&-  &"

skill#21: 使用xargs逐行处理标准输出

如果前一个命令的标准输出为文件列表, 逐个处理文件, 则可以用到xargs命令.

比如替换一组文件中的某一个特定的字符串.

ag -G '.*.(cc|cpp|c|C|hh|hpp|h|H)$' 'stdio.h' -l | xargs -i{} perl -i.bak -lpe 's/stdio.h/cstdio/g' '{}'

skill#22: 批量替换文件名

当文件名中包含空格或者其他不可打印字符时, 使用perl rename函数, 而非mv.

# 后缀.MD修改为.md
ag -G '.*.md$' -l |perl -lne 'chomp;rename $_ => "$1.md" if -f && /(.*).md$/'
# 文件名后缀添加.bak
ag -G '.*.md$' -l |perl -lne 'chomp;rename $_ => "$_.bak" if -f'
# 去掉文件名后缀back
ag -G '.*.bak$' -l |perl -lne 'chomp;rename $_=>$1 if /(.*).bak$/'

skill#23: 匹配除Windows Vista之外的其它Windows版本.

perl -lne 'print if /^Windowss+(?!Vista)/i' <<'DONE'
windows vista
windows xp
windows 2003
windows 95
DONE

skill#24: 平移copy, 将一个目录中所有文件平移到另外一个目录下.

方法1: 使用cpio命令.

#用户把 /root/gcc-5.4_prefix中的文件原封不动地平移到/usr下.
cd /root/gcc-5.4_prefix
find -type f |cpio -o > /root/gcc-5.4-bin.cpio
cd /usr
cpio -id < /root/gcc-5.4-bin.cpio

方法2:分别操作目录和文件

cd /root
find gcc-5.4_prefix -type d |perl -lpe 's{gcc-5.4_prefix}{/usr}g'|xargs -i{} mkdir -p '{}'
find gcc-5.4_prefix -type f |perl -lne '$src=$_; s{gcc-5.4_prefix}{/usr}g; qx(cp "$src" "$_")'

skill#25: bash中逻辑算符||和&&与C语言不同, 不具有优先级

: || echo "OK1" && echo "OK2" && echo "OK3"
# 依然会输出 OK2 OK3
# 上述语句等价于
((: || echo "OK1") && echo "OK2" )&& echo "OK3"

# 如果想获得和C一样的语意, 使用
: || (echo "OK1" && echo "OK2" && echo "OK3")

skill#26: 编写交互式工具 - 选择列表

selectOption(){
  test $# -gt 0
  select opt in $*;do
    echo ${opt}
    break;
  done
}

$a=$(selectOption "foobar" "bazz" "deadbeef")
echo $a

执行结果:
$ opt=$(selectOption "foobar" "bazz" "deadbeef")
1) foobar
2) bazz
3) deadbeef
#? 1
$ echo $opt
foobar

skill#27: 编写交互式工具 - 确认yes/no

confirm(){
  echo -n "Are your sure[yes/no]: "
    while : ; do
      read input
      input=$(perl -e "print qq/L${input}E/")
      case ${input} in
        y|ye|yes)
          break
          ;;
        n|no)
          echo "operation is cancelled!!!"
          exit 0
          ;;
        *)
          echo -n "invalid choice, choose again!!! [yes|no]: "
          ;;
      esac
    done
}

使用input=$(perl -e "print qq/L${input}E/")转小写,然后:

  1. 输入匹配到yes的前缀, 则继续执行;
  2. 输入匹配到no的前缀, 则输出"operation is cancelled!!!", 并且退出脚本;
  3. 其他输入, 均非法, 继续提示输入.

skill#28: shell opt选项save和restore

用在什么场景, 比如我们编写一个函数, 希望在这个函数局部地修改shell opt, 并且函数退出时, 恢复到原来的shell opt. 即函数的执行不影响整个脚本的shell opt.

 foobar(){
    local oldshopt=$(set +o)
    set -e -o pipefail
    ...
    set +vx;eval "${oldshopt}"
    echo ${result}
 }

使用set +o保存shell opt, 使用 set +vx; eval "${oldopt}"恢复老的opt.

skill#29: 通用的checkArgment函数

checkArgument(){
  local name=${1:?"missing 'name'"};shift
  local arg=${1:?"missing 'arg'"};shift
  local alternatives=${1:?"missing 'alternatives'"};shift

  if [ -z ${alternatives} ];then
    echo "ERROR: empty alternatives for '${name}', value='${arg}'" >&2
    exit 1
  fi

  if test x$(perl -e "print qq/${alternatives}/=~/^w+(?:|w+)*$/")x != x1x;then
    echo "ERROR: alternatives must be in format word1|word2|word3..., name='${name}', value='${arg}', alternatives='${alternatives}" >&2
    exit 2
  fi

  if test x$(perl -e "print qq/$arg/=~/^(?:${alternatives})$/")x != x1x; then
    echo "ERROR: unmatched argument, name='${name}', value='${arg}', alternatives='${alternatives}'" >&2
    exit 1
  fi
}

# e.g.
checkArgument "service" "master" "master|regionserver"

skill#30 date命令操作

date +"%s" #输出时间戳, 单位为秒
date +"%Y%m%d_%H%M%S" #输出20190818_163017
date +"%Y%m%d_%H%M%S" -d@1566116945 #时间戳1566116945转日期
perl -e 'print qx(date +"%s" -d "$1-$2-$3 $4:$5:$6") if qq(20190818_162905)=~/^(d{4})(d{2})(d{2})_(d{2})(d{2})(d{2})$/' #日期转时间戳

perl -e 'print time()' #输出时间戳
perl -MPOSIX=strftime -e 'print strftime "%Y%m%d_%H%M%S", localtime(1566117946)' #时间戳转日期
perl -MPOSIX=strftime -e 'print strftime "%Y%m%d_%H%M%S", localtime(time())' #时间戳转日期
perl -MTime::Piece -e 'print Time::Piece->strptime("20190818_164546", "%Y%m%d_%H%M%S")->strftime("%s")' #日期转时间戳

skill#31 彩色输出

shell脚本中, 需要用不同的颜色对失败或者成功进行彩色高亮输出, 可以提高运维脚本的好用性.

首先给出一个调色板, color_palette.pl.

#!/usr/bin/perl
use strict;
use warnings;

my @fg=(31..37,90..97);
my @bg=(40..47,100..106);
my @ef=(0..8);

for (1..@fg*@bg*@ef) {
  my $i=($_-1)/(@bg*@ef);
  my $j=($_-1)%(@bg*@ef)/@ef;
  my $k=($_-1)%@ef;
  my $fg=$fg[$i];
  my $bg=$bg[$j];
  my $ef=$ef[$k];
  print "e[${fg};${bg};${ef}m e[${fg};${bg};${ef}me[me[m";
  if ($_%10==0){
    print "n";
  } else{
    print " ";
  }
}

用户可以从调色板的输出结果中, 选择自己偏好的颜色.

首先, term的颜色怎么编码呢? 使用三元组.

前景色;背景上;特效
比如:"97;105;5",表色前景白色, 背景粉色,特效闪烁.

然后, 颜色作用的范围有起始标记, 被起始标记包围的文字会被彩色打印.

开始标记: <ESC>[dd;dd;dm

结束标记: <ESC>[m

比如要用“前景白色, 背景粉色,特效闪烁”输出deadbeef, 则使用下列ascii串

<ESC>[97;105;5mdeadbeef<ESC>[m

问题的关键是, 怎么转移后者输入<ESC>键呢?

C语言风格的printf使用e表示<ESC>, perl语言的print/printf函数, shell的printf, echo -e命令和C保持兼容. 因此在这三种场景下,输出上述彩打foobar, 使用命令

printf "e[97;105;5mdeadbeefe[m" # shell
perl -e 'printf "e[97;105;5mdeadbeefe[m"' # perl one-linar command
echo -e  "e[97;105;5mdeadbeefe[m" # -e enable escape-char

shell的echo命令录入ESC略有不同, 使用<CTRL-V><ESC>按键输入<ESC>键, 终端一般显示为^[, 记住直接输入^[并不管用.

echo "^[[97;105;5mdeadbeef^[[m" #使用<CTRL-V><ESC>输入<ESC>
echo -e  "e[97;105;5mdeadbeefe[m" #使用转义字符.

写一个小脚本(colorprint.sh)使用一下彩打.

#!/bin/bash
set -e -o pipefail
basedir=$(cd $(dirname $(readlink -f ${BASH_SOURCE:-$0}));pwd);
cd ${basedir}
arg=${1:?"^[[95;41;5mmissing 'arg'^[[m"};shift
if [ "x${arg}x" = "xfoobarx" ];then
   echo -e "e[32;100;1mOKe[m: arg is foobar"
else
   echo -e "e[31;100;1mERRORe[m: arg is not foobar"
fi

skill#32: 处理命令行参数

使用perl处理命令行参数

下面是使用fio, 从并发度和iosize两个维度持续加压, 测试磁盘性能的脚本.

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;

our ($OPT_concurrencyInit, $OPT_concurrencyLinearVarying, $OPT_concurrencyExponentialVarying) = (1, 0, 2);
our ($OPT_ioSizeInit, $OPT_ioSizeLinearVarying, $OPT_ioSizeExponentialVarying) = (256, 0, 1);
our ($OPT_fileSize, $OPT_directory, $OPT_timeout, $OPT_stopOnSaturation) = (1*2**19, "/mnt/nefs/0/fiotest", 7200, "true");
our ($OPT_concurrencyMax, $OPT_ioSizeMax) = (500, 64*2**10);

sub options(){ map {/^OPT_(w+)b$/; ("$1=s" => eval "*${_}{SCALAR}") } grep {/^OPT_w+b$/} keys %:: }

sub usage(){
	my $name = qx(basename $0); chomp $name;
	"USAGE:nt" . "$name " . join " ", map{/^OPT_(w+)$/; "--$1"} grep {/^OPT_w+b$/} keys %::;
}
sub show(){
	print join "n", map {/^OPT_(w+)b$/; ("--$1=" . eval "$$_" ) } grep {/^OPT_w+b$/} keys %::;
	print "n";
}

GetOptions(options()) or die usage();
show();

sub workloadGenerator{
	my ($C, $C_lvary, $C_evary, $IO, $IO_lvary, $IO_evary) = @_;
	sub(){
		my ($c, $io) = ($C, $IO);
		$C += $C_lvary;
		$C *= $C_evary;
		$IO += $IO_lvary;
		$IO *= $IO_evary;
		($c, $io);
	}
}


my $startup = time();
sub since{my $start=shift; time()-$start}

my $WLGen = workloadGenerator(
	$OPT_concurrencyInit, $OPT_concurrencyLinearVarying, $OPT_concurrencyExponentialVarying,
	$OPT_ioSizeInit, $OPT_ioSizeLinearVarying, $OPT_ioSizeExponentialVarying,
);
=pod
for (1..100){
my @a=$WLGen->();
print "@an";
}
=cut

sub normbw{
	my ($num, $unit)=split ",", shift;
	my %conv=("B"=>1, "KB"=>2**10, "MB"=>2**20, "GB"=>2**30);
	$num*$conv{$unit}/1024;
}
sub normlat{
	my ($num, $unit)=split ",", shift;
	my %conv=("usec"=>0.001, "msec"=>1, "sec"=>1000, "min"=>60000);
	$num*$conv{$unit};
}
qx(echo -n '' > result.dat);
qx(mkdir -p $OPT_directory);
my $fio_args="--ioengine=psync --sync=1 --direct=1 --group_reporting --unlink=1 --rw=write --directory=$OPT_directory";
my $count=0;
while(1){
	if (since($startup) > $OPT_timeout) { print "timeout:n"; exit 0; }
	my ($concurrency, $ioSize) = $WLGen->();
	if ($concurrency > $OPT_concurrencyMax || $ioSize > $OPT_ioSizeMax) { 
		print "concurrency=$concurrency;ioSize=$ioSizen";
		exit 0;
	}

	print qq(fio $fio_args --numjobs=$concurrency --name=bs${ioSize}K --bs=${ioSize}K --size=${OPT_fileSize}K > stdout),"n";
	qx(fio $fio_args --numjobs=$concurrency --name=bs${ioSize}K --bs=${ioSize}K --size=${OPT_fileSize}K > stdout);
	die $! if $?;
	qx(mv stdout stdout.${count});

	my $curr="stdout." . (${count}-0);
	my $curr_bw_unit = qx(perl -ne 'print "$1,$2" if/^s+write:.*bw=b(d+(?:.d+)?)s*(w+)b/' $curr);chomp $curr_bw_unit;
	my $curr_bw=normbw($curr_bw_unit);
	my $curr_iops = qx(perl -ne 'print "$1" if/^s+write:.*iops=b(d+)b/' $curr);chomp $curr_iops;
	my $curr_lat_unit= qx(perl -ne 'print "$2,$1" if/^s+lats*((w+)).*avg=b(d+(.d+)?)b/' $curr);chomp $curr_lat_unit;
	my $curr_lat=normlat($curr_lat_unit);
	qx(echo "$concurrencyt$ioSizet$curr_bwt$curr_iopst$curr_lat" >> result.dat);
	if ($OPT_stopOnSaturation eq "true" && $count > 0) {
		my $prev="stdout." . (${count}-1);
		my $prev_bw_unit = qx(perl -ne 'print "$1,$2" if/^s+write:.*bw=b(d+(?:.d+)?)s*(w+)b/' $prev);chomp $prev_bw_unit;
		my $prev_bw=normbw($prev_bw_unit);
		my $prev_iops = qx(perl -ne 'print "$1" if/^s+write:.*iops=b(d+)b/' $prev);chomp $prev_iops;
		my $prev_lat_unit= qx(perl -ne 'print "$2,$1" if/^s+lats*((w+)).*avg=b(d+(.d+)?)b/' $prev);chomp $prev_lat_unit;
		my $prev_lat = normlat($prev_lat_unit);

		my $delta=abs(($curr_bw - $prev_bw)/$curr_bw);
		if ($delta < 0.0005) {
			print "curr_bw=$curr_bw; prev_bw=$prev_bw; delta=$deltan";
			exit 0;
		}
	}
	$count++;
}

skill#33: 删除日志目录中的文件, 只保留近期3个文件

下面脚本是安全的,不会出现误删/或者~, 也不会出现多删除.

#!/bin/bash
set -e -o pipefail
basedir=$(cd $(dirname $(readlink -f ${BASH_SOURCE:-$0}));pwd)

dir=${1:?"undefined 'dir'"};shift
test -d ${dir}
dir=$(cd ${dir};pwd)
test ${dir} != "/"
test ${dir} != "${HOME}"

cd ${basedir}

echo "clean ${dir} ..."
filenum=$(ls -rt ${dir}|wc -l)
echo "${dir} has ${filenum} file(s)"
if [ ${filenum} -le 3 ];then
  exit 0
fi

for f in $(ls -rt ${dir}|head -n -3);do
  if [ ! -f ${dir}/${f} ];then
    echo ${dir}/${f} is not a file >&2
    continue
  fi
  echo rm ${dir:?"undefined 'dir'"}/${f:?"undefined 'f'"}
  rm ${dir:?"undefined 'dir'"}/${f:?"undefined 'f'"}
done

skill#34: shell编程参考书推荐

只推荐UNIX Shells by Example (4th Edition),推荐理由:

  1. 包括Unix/Linux系统启动后的初始化阶段的内容.
  2. 包括了bash, sh, ksh, tcsh四种shell方言.
  3. 讲解了shell中的pipe,redirection, fork, exec,dup的机制,可以结合APUE学习系统编程.
  4. 专门分章节讲解了sed/awk/grep的各种变种.
  5. 内容组织合理, 先给出了各种shell方言的不同,然后详细讲解了各种shell的细节.

整理思维导图:

https://github.com/satanson/freemind_docs/blob/master/shell_programming.mm

(未完待续...)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值