鹤啸九天 自律更自由,平凡不平庸 Less is More

Shell语言

2016-06-25
阅读量

Notes(温馨提示):

  1. ★ 首次阅读建议浏览:导航指南, 或划到本页末尾, 或直接点击跳转, 查看全站导航图
  2. 右上角工具条搜索文章,右下角二维码关注微信公众号(鹤啸九天),底栏分享、赞赏、评论
  3. ★ 转载请注明文章来源,知识点积累起来不容易,水滴石穿,绳锯木断,谢谢理解
  4. ★ 如有疑问,邮件讨论,欢迎贡献优质资料


Shell语言

更多编程语言介绍:

Shell 知识点

在 Linux 的基础上再度深入学习 Shell,可以极大的减少重复工作的压力。毕竟批量处理才是工作的常态呢~

总结

经验

Bash Shell,使用反斜线时,中途注释语句无效!

  • 【2024-4-11】 如下注释行无效
deepspeed --master_port 30001 ./llm/training/conversation_reward/main.py \
   --max_seq_len 2048 \
   --per_device_train_batch_size 1 \
   #--per_device_train_batch_size 2 \
   --per_device_eval_batch_size 2 \
   --weight_decay 0.01

常规语法

查看 Shell 版本

bash --version
echo "$BASH_VERSION"

执行shell

# ----- test.sh -----
#!/bin/bash
VAR="world"
echo "Hello $VAR!" # => Hello world!
# -------------------
# 执行
bash test.sh # 执行shell脚本
source "${0%/*}/../share/foo.sh" # 当前shell中执行
(cd somedir; echo "I'm now in $PWD") # 执行子shell
# 条件执行
git commit && git push # 成功才继续
git commit || echo "Commit failed" # 失败才继续
# 命令嵌入: ${}或``
echo "I'm in $(PWD)"
# Same as:
echo "I'm in `pwd`"

系统命令

history # 显示历史
sudo !! # 使用 sudo 运行上一个命令
shopt -s histverify # 不要立即执行扩展结果
!$ # 展开最新命令的最后一个参数
!* # 展开最新命令的所有参数
!-n # 展开第 n 个最近的命令
!n # 展开历史中的第 n 个命令
!<command> # 展开最近调用的命令 <command>
!! # 再次执行最后一条命令 
!!:s/<FROM>/<TO>/ # 在最近的命令中将第一次出现的 <FROM> 替换为 <TO> 
!!:gs/<FROM>/<TO>/ # 在最近的命令中将所有出现的 <FROM> 替换为 <TO> 
!$:t # 仅从最近命令的最后一个参数扩展基本名称 
!$:h # 仅从最近命令的最后一个参数展开目录 
# !! 和 !$ 可以替换为任何有效的扩展
# 切片 Slices
!!:n # 仅扩展最近命令中的第 n 个标记(命令为 0;第一个参数为 1) 
!^ # 从最近的命令展开第一个参数 
!$ # 从最近的命令中展开最后一个标记 
!!:n-m # 从最近的命令扩展令牌范围 
!!:n-$ # 从最近的命令中将第 n 个标记展开到最后
# 重定向
python hello.py > output.txt   # 标准输出到(文件)
python hello.py >> output.txt  # 标准输出到(文件),追加
python hello.py 2> error.log   # 标准错误到(文件)
python hello.py 2>&1           # 标准错误到标准输出
python hello.py 2>/dev/null    # 标准错误到(空null)
python hello.py &>/dev/null    # 标准输出和标准错误到(空null)
python hello.py < foo.txt      # 将 foo.txt 提供给 python 的标准输入

变量操作

sheel变量

默认值

# 默认值
${FOO:-val} # $FOO,如果未设置,则为 val
${FOO:=val} # 如果未设置,则将 $FOO 设置为 val
${FOO:+val} # val 如果设置了$FOO
${FOO:?message} # 如果 $FOO 未设置,则显示消息并退出

变量拼接

NAME="John"
echo ${NAME}    # => John (变量)
echo $NAME      # => John (变量)
echo "$NAME"    # => John (变量)
echo '$NAME'    # => $NAME (字符串原样输出),单引号时,不解析变量
echo "${NAME}!" # => John! (变量)
NAME = "John"   # => 报错:Error (注意不能有空格)
# {}扩展
echo {A,B} # 与 A B 相同
echo {A..D}.js # 与 A.js B.js C.js D.js相同
echo {1..5} # 与 1 2 3 4 5 相同
# 数值计算
a=2
$((a + 200))      # Add 200 to $a
$(($RANDOM%200))  # 随机数 Random number 0..199


# 注释:多行注释使用 :' 打开和 ' 关闭
: '
这是一个
非常整洁的
bash 注释
'
# Heredoc
cat <<END
hello world
END

特殊变量

#!/bin/bash

# 特殊变量
$? # 最后一个任务的退出状态
$! # 最后一个后台任务的 PID
$$ # shell PID
$0 # shell 脚本的文件名

echo "Process ID: $$"
echo "File Name: $0"
echo "First Parameter : $1"
echo "Second Parameter : $2"
echo "All parameters 1: $@"
echo "All parameters 2: $*"
echo "Total: $#"

控制判断

# 如何避免变量控制?
[ s"$choice" == "sy" ] # ①
[ $choice -a $choice == "y" ] # ②

变量作用域

变量的作用域(Scope)

Shell 变量作用域分三种:

  • 全局变量(global variable): 当前 Shell 会话中使用
  • 局部变量(local variable): 只能在函数内部使用
  • 环境变量(environment variable): 在其它 Shell 中使用。

解析

  • 全局变量:变量在当前的整个 Shell 会话中都有效。每个 Shell 会话都有自己的作用域,彼此之间互不影响。在 Shell 中定义的变量,默认就是全局变量。
  • 局部变量:Shell 函数中定义的变量默认是全局变量,它和在函数外部定义变量拥有一样的效果
  • 环境变量:环境变量被创建时所处的 Shell 被称为父 Shell,如果在父 Shell 中再创建一个 Shell,则该 Shell 被称作子 Shell。当子 Shell 产生时,它会继承父 Shell 的环境变量为自己所用,所以说环境变量可从父 Shell 传给子 Shell。
    • 注意,环境变量只能向下传递而不能向上传递,即“传子不传父”
a=3 # 全局变量
global a=3 # 报错,没有global关键词
export a # 将自定义变量设定为系统环境变量

fun f(){
    c='1' # 全局变量
    local t='2' # 局部变量,仅函数内使用
}
local t='2' # 报错,local必须出现在函数内

export

Linux export 命令用于设置/显示环境变量。

shell 中执行程序时,shell 会提供一组环境变量。export 可新增,修改或删除环境变量,供后续执行的程序使用。

export 命令的作用域:

当前终端中直接输入的 export 变量仅当前shell终端及其子shell可见,另起一个终端将无法访问。

注意

  • 父shell中的export变量,子shell可读
  • 子shell变量是父shell的一个拷贝,不影响父shell取值
  • 子shell export变量,父shell读不到
export WORD="hello"

echo $WORD           # 可以看到输出 hello
env | grep WORD      # 可以看到有WORD变量
sh -c "echo $WORD"   # 子shell中执行,同样可以看到输出了 hello

如何保证其他终端可见?

  • ~/.bashrc 或者 /etc/profile 中使用 export 命令配置全局的环境变量,然后source,所有终端都可见

变量传递

如何从子shell向父shell传递变量

  • 命令传输
  • 中间文件
  • 管道
  • here文档
# 命令传输
pvar=`subvar='hello shell'`
echo $pvar
# 文件传输
(
    subvar='hello shell'
    echo "$subvar" > tmp.txt
)
read pvar < tmp.txt
echo $pvar
# 管道传输
mkfifo -m 777 npipe # 创建管道 npipe
(
    subsend="hello world"
    echo "$subsend" > npipe &
)
read pread < npipe
echo "$pread"
# here 文档
read pvar <<HERE
`subvar="hello shell"
echo $subvar`
HERE
echo $pvar

函数参数

函数

# function get_name() { # function 关键字可以省略
get_name() {
    echo "John $1"
}
echo "You are $(get_name)"
# 带返回值的函数
myfunc() {
    local myresult='some value'
    echo $myresult
}
result="$(myfunc)"

参数

【2024-7-22】参数解析, 详见

  • 手工处理: $1~${10}
  • getopts: shell内置,不支持长选项, 如: –a=1
  • getopt: 外部工具,支持长选项

getoptgetopts 都是 Bash 中获取命令行参数的工具。

两者比较

  • (1)getopts 是 Shell 内建命令, getopt 是一个独立外部工具
  • (2)getopts 使用语法简单, getopt 使用语法较复杂
  • (3)getopts 不支持长参数(如:–option ),getopt 支持
  • (4)getopts 不会重排所有参数的顺序,getopt 会重排参数顺序
  • (5)getopts 为了代替 getopt 较快捷的执行参数分析工作
$1
$0 # 脚本本身的名称
$1$9 # 参数 1 ... 9
$1 # 第一个参数
${10} # 位置参数 10
$# # 参数数量
$$ # shell 的进程 id
$* # 所有参数
$@ # 所有参数,从第一个开始
$- # 当前选项
$_ # 上一个命令的最后一个参数
getopts

用法

#!/bin/bash

# 选项后面的冒号, 表示该选项需要参数
while getopts "a:bc" arg
do
        case $arg in
             a)
                echo "a's arg:$OPTARG" #参数存在$OPTARG中
                argument1=$OPTARG
                ;;
             b)
                echo "b"
                branch=1
                ;;
             c)
                echo "c"
                iscar=1
                ;;
             ?)  #当有不认识的选项的时候arg为?
            echo "unkonw argument"
        exit 1
        ;;
        esac
done

# 使用
./test.sh -a arg -b -c
./test.sh -a arg -bc

局限:

  1. 选项参数的格式必须是-d val,中间没有空格的-dval, 不行。
  2. 所有选项参数必须写在其它参数的前面,因为getopts是从命令行前面开始处理,遇到非-开头的参数,或者选项参数结束标记--就中止了,如果中间遇到非选项的命令行参数,后面的选项参数就都取不到了。
  3. 不支持长选项, 也就是--debug之类的选项

【2024-7-22】 实践

cur_dir=`pwd`
project='change_query' # session_cut
mode='train'
# 参数解析
while getopts "e:p:m" arg
do
   case $arg in
      e)
         echo "环境路径(environment), $OPTARG";cur_dir=${OPTARG};;
      p)
         echo "项目名(project): $OPTARG";project=$OPTARG;;
      m)
         echo "运行模式(mode): $OPTARG";model='infer';;
      ?)
         echo "未知参数: $OPTARG";break;;
   esac
done
echo -e "[参数解析]: \n\t环境路径(environment)\t${cur_dir}\n\t项目名(project)\t${project}\n\t运行模式(mode)\t${mode}"
改进 getopt

外部改进版

# 经过getopt 处理,具体选项。
while true; 
do
    case "$1" in
        -a|--a-long) echo "Option a" ; shift ;;
        -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;;
        -c|--c-long)
            # c has an optional argument. As we are in quoted mode,
            # an empty parameter will be generated if its optional
            # argument is not found.
            case "$2" in
                "") echo "Option c, no argument"; shift 2 ;;
                *)  echo "Option c, argument \`$2'" ; shift 2 ;;
            esac ;;
        --) shift ; break ;;
        *) echo "Internal error!" ; exit 1 ;;
    esac
done
echo "Remaining arguments:"
for arg do
   echo '--> '"\`$arg'" ;
done


#!/bin/bash
 
ARGS=`getopt -o "ao:" -l "arg,option:" -n "getopt.sh" -- "$@"`
 
eval set -- "${ARGS}"
 
while true; do
    case "${1}" in
        -a|--arg)
            shift;
            echo -e "arg: specified"
            ;;
        -o|--option)
            shift;
            if [[ -n "${1}" ]]; then
                echo -e "option: specified, value is ${1}"
                shift;
            fi
            ;;
        --)
            shift;
            break;
            ;;
    esac


# 使用
./test -a  -b arg arg1 -c
# 命令行中多了个arg1参数,在经过getopt和set之后,命令行会变为:
-a -b arg -c -- arg1
# $1指向-a,$2指向-b,$3指向arg,$4指向-c,$5指向--,而多出的arg1则被放到了最后。

控制

操作系统差异

debain(乌班图)linux 下,字符串判断时,会出现以下错误

  • 因为 ubuntu 默认 shell 使用 dash,dash 与 bash 一些指令上有些差异
  • 解法: 改回 /bin/sh -> bash,修改默认sh
    • sudo dpkg-reconfigure dash 启动可视化编辑页面
    • 然后选 No,改回 /bin/sh -> bash
    • 把 == 改成 =
[: =~: binary operator expected
[[: =~: binary operator expected

if 条件判断

# 字符串比较
[[ -z STR ]] # 空字符串
[[ -n STR ]] # 非空字符串
[[ STR == STR ]] # 相等
[[ STR = STR ]] # 相等(同上)
[[ STR < STR ]] # 小于 (ASCII)
[[ STR > STR ]] # 大于 (ASCII)
[[ STR != STR ]] # 不相等
[[ STR =~ STR ]] # 正则表达式
# 整数比较
[[ NUM -eq NUM ]] # 等于 Equal
[[ NUM -ne NUM ]] # 不等于 Not equal
[[ NUM -lt NUM ]] # 小于 Less than
[[ NUM -le NUM ]] # 小于等于 Less than or equal
[[ NUM -gt NUM ]] # 大于 Greater than
[[ NUM -ge NUM ]] # 大于等于 Greater than or equal
(( NUM < NUM )) # 小于
(( NUM <= NUM )) # 小于或等于
(( NUM > NUM )) # 比...更大
(( NUM >= NUM )) # 大于等于
# 文件
[[ -e FILE ]] # 存在
[[ -d FILE ]] # 目录
[[ -f FILE ]] # 文件
[[ -h FILE ]] # 符号链接
[[ -s FILE ]] # 大小 > 0 字节
[[ -r FILE ]] # 可读
[[ -w FILE ]] # 可写
[[ -x FILE ]] # 可执行文件
[[ f1 -nt f2 ]] # f1 比 f2 新
[[ f1 -ot f2 ]] # f2 比 f1 新
[[ f1 -ef f2 ]] # 相同的文件
# 高级
[[ X && Y ]] # 条件组合
[[ "$A" == "$B" ]] # 是否相等
[[ -o noclobber ]] # 如果启用 OPTION
[[ ! EXPR ]] # 不是 Not
[[ X && Y ]] # 和 And
[[ X || Y ]] # 或者 Or
# 整型
if (( $a < $b )); then
   echo "$a is smaller than $b"
fi
# 字符串
if [[ -z "$string" ]]; then
    echo "String is empty"
elif [[ -n "$string" ]]; then
    echo "String is not empty"
fi
# 正则表达式
if [[ '1. abc' =~ ([a-z]+) ]]; then
    echo ${BASH_REMATCH[1]}
fi

# 文件是否存在
if [[ -e "file.txt" ]]; then
    echo "file exists"
fi
# 逻辑运算
if [ "$1" = 'y' -a $2 -gt 0 ]; then
    echo "yes"
fi
if [ "$1" = 'n' -o $2 -lt 0 ]; then
    echo "no"
fi

复合条件判断

  • && ||-a –o 处理
#!/bin/bash

# 等效表达: [[]] = []
[  exp1  -a exp2  ] = [[  exp1 && exp2 ]] = [  exp1  ]&& [  exp2  ] = [[ exp1  ]] && [[  exp2 ]]
[  exp1  -o exp2  ] = [[  exp1 || exp2 ]] = [  exp1  ]|| [  exp2  ] = [[ exp1  ]] || [[  exp2 ]]
# [2024-7-22]
read -p "是否使用如下配置? [y/n]" choice
echo "你选择了: $choice"
# 如何避免变量控制?
[ s"$choice" == "sy" ] # ①
[ $choice -a $choice == "y" ] # ②
[ $choice -a $choice == "y" ] && {  echo "开始训练..."; } || { echo "不训练, 退出...";exit 0; }

# 整型比较
count="$1"
if [ $count -gt 15 -o $count -lt 5 ];then
   echo right
fi
# 字符串
if [[ "a" == "a" ]] || [[ 2 -gt 1]] ;then
    echo "ok";
fi

实测

  • === 等效
  • []test 等效
  • [[]][]大体等效,但正则表达式只能用双括号
  • = 两边要留空,否则结果错误
echo "单括号(识别错误)"
if [ "1"="" ] && [ "1"="1" ]; then echo A; else echo B; fi # A
if [ "1"="" -a "1"="1" ]; then echo A; else echo B; fi # A
echo "留空格(正确)"
if [ "1" = "" ] && [ "1" = "1" ]; then echo A; else echo B; fi # B
if [ "1" = "" -a "1" = "1" ]; then echo A; else echo B; fi # B
echo "双括号"
if [[ "1"="" ]] && [[ "1"="1" ]]; then echo A; else echo B; fi # A
if [[ "1" = "" ]] && [[ "1" = "1" ]]; then echo A; else echo B; fi # B
# if [[ "1"="" -a "1"="1" ]]; then echo A; else echo B; fi # 报错
echo "双等号(正确)"
if [ "1" == "" ] && [ "1" == "1" ]; then echo A; else echo B; fi # B
if [ "1" == "" -a "1" == "1" ]; then echo A; else echo B; fi # B
# 正则表达式需要双括号,否则报错
if [[ ${BASEDIR} =~ ^hdfs ]];then
    echo "y"
else
    echo 'n'
fi
# 组合条件中, 正则表达式单独使用[[]]括起来,不能混用!
# if [ $mode = 'local' -a ${BASEDIR} =~ ^hdfs ];then
if [ $mode = 'local' ] && [[ ${BASEDIR} =~ ^hdfs ]]; then
    echo "y"
else
    echo 'n'
fi

for 循环判断

# 基本 for 循环
for i in /etc/rc.*; do
    echo $i
done
# 类似 C 的 for 循环
for ((i = 0 ; i < 100 ; i++)); do
    echo $i
done
for i in `ls`;
do 
    echo $i is file name\! ;
done
# 字符串,[2023-2-4]注意:不能省略变量name_list,直接用字符串
name_list="httpd-init.service  httpd.service  httpd.socket  httpd@.service httpd.service.d";
for file in $name_list;
do
        echo "[$file]"
done
# 范围
for i in {1..5}; do
# for i in {5..50..5}; do # 设置步长
    echo "Welcome $i"
done
# awk版
awk 'BEGIN{for(i=1; i<=10; i++) print i}'
# 自动递增
i=1
while [[ $i -lt 4 ]]; do
    echo "Number: $i"
    ((i++))
done
# 自动递减
i=3
while [[ $i -gt 0 ]]; do
    echo "Number: $i"
    ((i--))
done
# Continue, break
for number in $(seq 1 3); do
    if [[ $number == 2 ]]; then
        continue;
        # break;
    fi
    echo "$number"
done
# Until
count=0
until [ $count -gt 10 ]; do
    echo "$count"
    ((count++))
done
# 死循环
# while :; do # 简写
while true; do
    # here is some code.
done

Case

类似 switch 用法

case "$1" in
    start | up)
    vagrant up
    ;;
    *)
    echo "Usage: $0 {start|stop|ssh}"
    ;;
esac

【2024-7-22】 实践

[ $# -lt 1 ] && { cur_dir=`pwd`; echo "未指定环境, 开始自动检测"; } || { cur_dir=$1; echo "使用传入的参数环境, $1"; }
env="cn"
case $cur_dir in 
   *"flow-algo-cn"*) env="cn"; echo "国内环境";;
   *"flow-algo-intl"*) env="va"; echo "美国环境";;
   *"flow-algo-my2"*) env="my2"; echo "马来西亚环境";;
   *) echo "默认环境";;
esac

时间日期

date用法

cal # 日历

date +%Y%m%d 
date +%F
now=$(date +%y%m%d) # 获取今天时期
date +%Y%m%d%H%M%S # 当前时间,时分秒
date +%m%d%H%M%y.%S # 具体到毫秒
date +%w    # 今天是星期几
date +%W   # 当前为今年的第几周
# 时间戳
date +%s   # 获取时间戳
date -d @1521563928  # 将时间戳换算成日期
date +%s -d "2017-03-21 00:38:48" # 将日期换算成时间戳

# 相对时间
date -d 'now'    #显示当前时间
date -d tomorrow +%y%m%d # 明天
date -d yesterday +%Y%m%d # 获取昨天时期
date -d '30 second ago'    #显示30秒前的时间
date -d -2day +%Y%m%d # 获取前天日期
date -d '7 days ago' +%Y%m%d # 一星期前
date -d '1 weeks ago' +%Y%m%d # 一星期前
date -d '2 days ago'    #显示2天前的时间
date -d -10day +%Y%m% # 获取10天前的日期
date -d `date +%y%m01`"-1 day" # 上月最后一天
date -d '3 month 1 day'    # 显示3月零1天以后的时间
date -d "n days ago" +%y%m%d # 或n天前的
date -d '25 Dec' +%j    #显示12月25日在当年的哪一天
# [2024-7-18] 设置参考日期(20231010), 前3天
date -d '3 days ago 20231010' +%Y%m%d

date -d "-3 day" # 3天前
date -d "+3 month"  # 3月后
date -d "-3 year" # 3年前,类似的,hour、minute、second
date -d `date -d "-3 month" +%y%m01`"-1 day" # 4个月前最后一天
date -d `date -d "+12 month" +%y%m01`"-1 day" # 11个月后第一天
date -d `date -d "+12 month" +%y%m01`"-1 day" +%Y%m%d # 11个月前最后一天

字符串

特殊的字符串处理方法

  • #前 %后
# ----- 总结 -----
${FOO%suffix} # 删除后缀
${FOO#prefix} # 删除前缀
${FOO%%suffix} # 去掉长后缀
${FOO##prefix} # 删除长前缀
${FOO/from/to} # 替换第一个匹配项
${FOO//from/to} # 全部替换
${FOO/%from/to} # 替换后缀
${FOO/#from/to} # 替换前缀
echo ${food:-Cake}  #=> 如果没定义food变量,就赋默认值(Cake) $food or "Cake" 
${FOO:0:3} # 子串 (位置,长度)
${FOO:(-3):3} # 从右边开始的子串
${#FOO} # $FOO 的长度

# -------------
STR="/path/to/foo.cpp"
echo ${STR%.cpp}    # /path/to/foo 删短后缀
echo ${STR%.cpp}.o  # /path/to/foo.o
echo ${STR%/*}      # /path/to
echo ${STR##*.}     # cpp (extension)
echo ${STR##*/}     # foo.cpp (basepath)
echo ${STR#*/}      # path/to/foo.cpp
echo ${STR##*/}     # foo.cpp
echo ${STR/foo/bar} # /path/to/bar.cpp

url="http://c.biancheng.net/index.html"
echo ${url#*/}    # 结果为 /c.biancheng.net/index.html
echo ${url##*/}   # 结果为 index.html
str="---aa+++aa@@@"
echo ${str#*aa}   # 结果为 +++aa@@@
echo ${str##*aa}  # 结果为 @@@
echo ${url%/*}  # 结果为 http://c.biancheng.net
echo ${url%%/*}  # 结果为 http:
str="---aa+++aa@@@"
echo ${str%aa*}  # 结果为 ---aa+++
echo ${str%%aa*}  # 结果为 ---
# ---- 切片 -----
# 切片 Slicing
name="John"
echo ${name}           # => John
echo ${name:0:2}       # => Jo
echo ${name::2}        # => Jo
echo ${name::-1}       # => Joh
echo ${name:(-1)}      # => n 逆序
echo ${name:(-2)}      # => hn 逆序
echo ${name:(-2):2}    # => hn 逆序
length=2
echo ${name:0:length}  # => Jo
# 大小写转换:,小写 ^大写
STR="HELLO WORLD!"
echo ${STR,}      # => hELLO WORLD! 首字母小写
echo ${STR,,}     # => hello world! 全部小写
STR="hello world!"
echo ${STR^}      # => Hello world! 首字符大写
echo ${STR^^}     # => HELLO WORLD! 全部大写
ARR=(hello World)
echo "${ARR[@],}" # => hello world 数组元素首字母小写
echo "${ARR[@]^}" # => Hello World 数组元素首字母大写

字符串包含

【2024-7-18】实测结果

cur_dir=$(pwd)

# 通配符 [[ $A == *$B* ]]
# 正则匹配
[[ $cur_dir =~ "change_query" ]] && { 
    echo "目标目录: $cur_dir"
} || {
    echo "非目标目录, 退出: $cur_dir"
    exit 0
}

main_dir="${cur_dir%%/change_query}"
# main_dir='/mnt/bn/flow-algo-intl/wangqiwen'
# 项目主目录
project_dir="${main_dir}/change_query"
data_dir="${project_dir}/data"
# 识别结果
echo -e "自动识别结果:\n本地主目录: ${main_dir}\n项目主目录: ${project_dir}\n数据目录: ${data_dir}"
方法一:grep查找
strA="long string"
strB="string"
result=$(echo $strA | grep "${strB}")
if [[ "$result" != "" ]]
then
    echo "包含"
else
    echo "不包含"
fi

先打印长字符串,然后在长字符串中 grep 查找要搜索的字符串,用变量result记录结果,如果结果不为空,说明strA包含strB。如果结果为空,说明不包含。

这个方法充分利用了grep 的特性,最为简洁。

方法二:正则

正则表达式

strA="helloworld"
strB="low"
if [[ $strA =~ $strB ]]
then
    echo "包含"
else
    echo "不包含"
fi

利用字符串运算符 =~ 直接判断strA是否包含strB。

方法三:通配符
A="helloworld"
B="low"
if [[ $A == *$B* ]]
then
    echo "包含"
else
    echo "不包含"
fi

用通配符*号代理strA中非strB的部分,如果结果相等说明包含,反之不包含。

方法四:case in 语句
thisString="1 2 3 4 5"    # 源字符串
searchString="1 2"        # 搜索字符串
case $thisString in 
    *"$searchString"*) echo  "包含";;
    *) echo  "不包含" ;;
esac    
方法五:字符串替换

替换子串后,字符串是否跟原来相同

STRING_A="helloworld"
STRING_B="low"
if [[ ${STRING_A/${STRING_B}//} == $STRING_A ]]
then
    echo  "不包含"
else
    echo  "包含"
fi

数组

  • 代码:
string="hello,shell,split,test" 
#将,替换为空格 
array=(${string//,/ })  # 空格区分,用()转数组
array=(`echo $string | tr ',' ' '` ) # 方法2
# 元素遍历
for var in ${array[@]} # 元素值遍历
do
   echo $var
done
for i in "${!array[@]}"; do # 下标遍历
  printf "%s\t%s\n" "$i" "${array[$i]}"
done

Fruits=('Apple' 'Banana' 'Orange') # 一次性定义
Fruits[0]="Apple" # 逐个赋值
Fruits[1]="Banana"
Fruits[2]="Orange"
# 批量定义
ARRAY1=(foo{1..2}) # => foo1 foo2
ARRAY2=({A..D})    # => A B C D
# 合并 => foo1 foo2 A B C D
ARRAY3=(${ARRAY1[@]} ${ARRAY2[@]})
# 声明构造
declare -a Numbers=(1 2 3)
Numbers+=(4 5) # 附加 => 1 2 3 4 5
# 索引含义
${Fruits[0]} # 第一个元素
${Fruits[-1]} # 最后一个元素
${Fruits[*]} # 所有元素
${Fruits[@]} # 所有元素 ""包围每个元素
${#Fruits[@]} # 总数
${#Fruits} # 第一节长度
${#Fruits[3]} # 第n个长度
${Fruits[@]:3:2} # 范围
${!Fruits[@]} # 所有 Key,索引index
# 数组操作
Fruits=("${Fruits[@]}" "Watermelon")         # 添加
Fruits+=('Watermelon')                       # 也是添加
Fruits=( ${Fruits[@]/Ap*/} )                 # 通过正则表达式匹配删除
unset Fruits[2]                              # 删除一项
Fruits=("${Fruits[@]}")                      # 复制
Fruits=("${Fruits[@]}" "${Veggies[@]}")      # 连接
lines=(`cat "logfile"`)                      # 从文件中读取
# 数组作为参数
function extract()
{
  local -n myarray=$1
  local idx=$2
  echo "${myarray[$idx]}"
}
Fruits=('Apple' 'Banana' 'Orange')
extract Fruits 2     # => Orangle

字典

declare -a sounds # 生命字典
# declare -A sounds # 生命字典, A在mac下执行失败
sounds[dog]="bark" # 赋值
sounds[cow]="moo"
# 取值
echo ${sounds[dog]} # Dog's sound
echo ${sounds[@]}   # All values 所有取值
echo ${!sounds[@]}  # All keys 所有key
echo ${#sounds[@]}  # Number of elements 元素个数
unset sounds[dog]   # Delete dog
# 遍历元素
for val in "${sounds[@]}"; do
    echo $val
done
for key in "${!sounds[@]}"; do
    echo $key
done

彩色日志

shell彩色文字

  • 特效格式
    • \033[0m 关闭所有属性
    • \033[1m 设置高亮度
    • \03[4m 下划线
    • \033[5m 闪烁
    • \033[7m 反显
    • \033[8m 消隐
    • \033[30m ~ \033[37m 设置前景色
    • \033[40m ~ \033[47m 设置背景色
  • 光标位置
    • \033[nA 光标上移n行
    • \03[nB 光标下移n行
    • \033[nC 光标右移n行
    • \033[nD 光标左移n行
    • \033[y;xH设置光标位置
    • \033[2J 清屏
    • \033[K 清除从光标到行尾的内容
    • \033[s 保存光标位置
    • \033[u 恢复光标位置
    • \033[?25l 隐藏光标
    • \33[?25h 显示光标
  • 特效可以叠加,需要使用“;”隔开
    • 例如:闪烁+下划线+白底色+黑字为 \033[5;4;47;30m闪烁+下划线+白底色+黑字为\033[0m
编码 颜色/动作
  0 重新设置属性到缺省设置
  1 设置粗体
  2 设置一半亮度(模拟彩色显示器的颜色)
  4 设置下划线(模拟彩色显示器的颜色)
  5 设置闪烁
  7 设置反向图象
  22 设置一般密度
  24 关闭下划线
  25 关闭闪烁
  27 关闭反向图象
  30 设置黑色前景
  31 设置红色前景
  32 设置绿色前景
  33 设置棕色前景
  34 设置蓝色前景
  35 设置紫色前景
  36 设置青色前景
  37 设置白色前景
  38 在缺省的前景颜色上设置下划线
  39 在缺省的前景颜色上关闭下划线
  40 设置黑色背景
  41 设置红色背景
  42 设置绿色背景
  43 设置棕色背景
  44 设置蓝色背景
  45 设置紫色背景
  46 设置青色背景
  47 设置白色背景
  49 设置缺省黑色背景
# !/bin/bash

#【2021-12-28】
echo -e "\033[4;31m 下划线红字 \033[0m" 
echo -e "\033[5;34m 红字在闪烁 \033[0m" #闪烁
echo -e "\033[8m 消隐 \033[0m " #反影

# 彩色文字设置
prefix="\033["
Color_Off="0m" # Text Reset
# Bold High Intensty
BIBlack="30m" # "[1;90m" # Black
BIRed="31m" # "[1;91m" # Red
BIGreen="32m" # "[1;92m" # Green
BIYellow="33m" # "[1;93m" # Yellow
BIBlue="34m" # "[1;94m" # Blue
BIPurple="35m" # "[1;95m" # Purple
BICyan="36m" # "[1;96m" # Cyan
BIWhite="37m" # "[1;97m" # White
 
echo ${BRed} "===开始检测===!" ${Color_Off}
function log(){
    [ $# -eq 0 ]&& { echo "date [`"+%Y-%m-%d %H:%M:%S"`] 请输入要打印的日志!";exit 1; }
    [ $# -eq 1 ]&& { level="INFO";log_info=$1; }
    [ $# -gt 1 ]&& { level=$1;log_info=$2; }
    #echo "[`"+%Y-%m-%d %H:%M:%S"`] [$level] $log_info"
    cur_color="$BIWhite"
    case $level in
        "INFO") cur_color="$BIWhite";;
        "WARNING") cur_color="$BIYellow";;
        "ERROR") cur_color="$BIPurple";;
        "FETAL") cur_color="$BIRed";;
        *) cur_color="$BIWhite";;
    esac
	# echo -e "\033[4;31;42m 文字 \033[0m"
    echo -e  "${prefix}${cur_color} [`date "+%Y-%m-%d %H:%M:%S"`] [$level] $log_info ${prefix}${Color_Off}"
}
log "INFO" "TEST INFO"
log "WARNING" "TEST WARNING"
log "ERROR" "TEST ERROR"
log "FETAL" "TEST FETAL"

# ------- 失效 -------
【2018-10-12】
# 彩色文字设置
Color_Off="[0m" # Text Reset
# Bold High Intensty
BIBlack="[1;90m" # Black
BIRed="[1;91m" # Red
BIGreen="[1;92m" # Green
BIYellow="[1;93m" # Yellow
BIBlue="[1;94m" # Blue
BIPurple="[1;95m" # Purple
BICyan="[1;96m" # Cyan
BIWhite="[1;97m" # White
 
echo ${BRed} "===开始检测===!" ${Color_Off}
function log(){
    [ $# -eq 0 ]&& { echo "date [`"+%Y-%m-%d %H:%M:%S"`] 请输入要打印的日志!";exit 1; }
    [ $# -eq 1 ]&& { level="INFO";log_info=$1; }
    [ $# -gt 1 ]&& { level=$1;log_info=$2; }
    #echo "[`"+%Y-%m-%d %H:%M:%S"`] [$level] $log_info"
    cur_color="$BIWhite"
    case $level in
        "INFO") cur_color="$BIWhite";;
        "WARNING") cur_color="$BIYellow";;
        "ERROR") cur_color="$BIPurple";;
        "FETAL") cur_color="$BIRed";;
        *) cur_color="$BIWhite";;
    esac
    echo ${cur_color} "[`date "+%Y-%m-%d %H:%M:%S"`] [$level] $log_info" ${Color_Off}
}

随机抽奖

Shell随机创建手机号码与随机抽奖:

  • 随机生成1000个以136开头的手机号码
#! usr/bin/bash

# 生成的手机号码存放到指定的目录的文件
filePath="/home/phoneNum.txt"

#(())语法类似C语法的括号
for((i=1;i<=1000;i++))
do
	# 取模
	num1=$[$RANDOM%10] # $RANDOM代表随机函数,是操作系统的全局变量
	num2=$[$RANDOM%10]
	num3=$[$RANDOM%10]
	num4=$[$RANDOM%10]
	num5=$[$RANDOM%10]
	num6=$[$RANDOM%10]
	num7=$[$RANDOM%10]
	num8=$[$RANDOM%10]
	num9=$[$RANDOM%10]
	# 将结果输到指定的文件里面
	echo "136$num1$num2$num3num4$num5$num6$num7$num8$num9" >> $filePath
done

# 寻找第100个的手机号码
head -100 phoneNum.txt | tail -1
# 查看是否有重复的手机号码
cat phoneNum.txt | sort -u|wc -l

# ================
# 抽取幸运的5位手机号码并去除已经抽取过的用户

#! usr/bin/bash
filePath="/home/phoneNum.txt"

# 循环读取5位用户手机号码
for((i=1;i<=5;i++))
do
	# 定位幸运观众所在行数
	#line=`wc -l $filePath | cut -d ' ' -f1`
  line=`wc -l $filePath | awk '{print $1}'
	#计算幸运行
	luck_line=$[RANDOM%line+1]
	#取出幸运观众所在行的电话号码,-1代表是一个
	luck_num=`head -$luck_line $filePath | tail -1` 
	#显示到屏幕,截取后位的号码数字
	echo "136****${luck_num:7:4}"
	# 将抽中的手机号码放到一个文本
	echo $luck_num >> luck.txt
	# 将抽中的手机号码在文本删除,防止下一次又抽中
	sed -i "/$luck_num/d" $filePath
done
# 查看还有多少行手机号码
wc -l /home/phoneNum.txt 

输出

系统输出

echo 和 printf

echo -n "Proceed? [y/n]: "
echo -e "a\tb" # 支持转移
read ans # 读入到变量 ans
read -n 1 ans    # 只有一个字符
echo $ans

printf
printf "Hello %s, I'm %s" Sven Olga #=> "Hello Sven, I'm Olga
printf "1 + 1 = %d" 2 #=> "1 + 1 = 2"
printf "Print a float: %f" 2 #=> "Print a float: 2.000000"

文件读取

cat file.txt | while read line; do
    echo $line
done

yaml 文件

yaml 示例

configuration:
  account: account1
  warehouse: warehouse1
  database: database1
  object_type:
    schema: schema1
    functions: funtion1
    tables:
      - table: table1
        sql_file_loc: some_path/some_file.sql
      - table: table2
        sql_file_loc: some_path/some_file.sql

代码1

#! /bin/bash

# 关键词
key="vehicle_type"

#  yaml文件位置
yaml_name="/home/xxxx/vehicle_param/vehicle_params.yaml"


function read_key(){
    flag=0
    # 逐行读取内容
    cat $1 | while read LINE
    do 
        if [ $flag == 0 ];then
            # 属性开始标志 vehicle_type:
            if [ "$(echo $LINE | grep "$key:")" != "" ];then
                if [ "$(echo $LINE | grep -E ' ')" != "" ];then
                    # 截取出key值
                    echo "$LINE" | awk -F " " '{print $2}'
                    continue
                else
                    # 如果关键词后面没有空格,则跳出继续查找
                    continue
                fi
            fi
        fi
    done
}

value=($(read_key $yaml_name))
echo ${value}

代码2

function parse_yaml {
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p"  $1 |
   awk -F$fs '{
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) {if (i > indent) {delete vname[i]}}
      if (length($3) > 0) {
         vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
      }
   }'
}

随机抽样

shell 中随机抽取数据,有多种方法

bash

bash 随机数

random=${RANDOM} # 25256

sort

「sort」命令的「-R」选项。

sort -R filename | head -n 100

例如,以下命令会随机抽取文件「data.txt」中的一行:

shuf

shuf 类似sort的命令行实用程序,包含在Coreutils中

Mac 需要单独安装

brew install coreutils

shuf 命令

shuf -n 100 data.txt
shuf -e line1 line2 line3 line4 line5 # 指定输入集合(字符串)
shuf -e 1 2 3 4 5 # 指定输入集合 1~5
shuf -n 3 -e 1 2 3 4 5 # 从指定集合中抽取3个
shuf -i 1-10 # 指定范围: 1~10

awk

或用「awk」命令,例如:

awk 'BEGIN{srand()} {if(rand()<0.01) print $0}' data.txt
awk 'BEGIN{srand()} {print rand()"\t"$0}' filename | sort -nk 1 | head -n100 | awk -F '\t' '{print $2}' # 假如输出的内容只有一列

这两个命令都可以随机地从文件中抽取一行数据。

#!/bin/bash

IN_FILE=$1
LINE_NUM=$2
awk -vN=${LINE_NUM} -vC="`wc -l ${IN_FILE}`" 'BEGIN{srand();while(n<N){i=int(rand()*C+1);if(!(i in a)){a[i]++;n++}}}NR in a' ${IN_FILE}

抽样

sh my_shuf.sh datas.txt 100 | awk '{print $1}' > random_data_100

三剑客

grep 、sed、awk被称为linux中的”三剑客”。

  • grep 更适合单纯的查找或匹配文本
  • sed 更适合编辑匹配到的文本
  • awk 更适合格式化文本,对文本进行较复杂格式处理

编码转换

  • 【2021-6-4】linux下转换文件编码格式,命令:
# 从gbk转utf8
iconv -f gbk -t utf8 pattern_0603.txt -o pattern.txt
# 上面命令失败的用下面
iconv -f gbk -t utf8 pattern_0603.txt > pattern.txt

grep

文件内容查找

if grep -q 'foo' ~/.bash_history; then
    echo "您过去似乎输入过“foo”"
fi
# 文件内容查找: 目录wqw里查找包含yes的文件
grep yes /wqw

# 从文件内容查找与正则表达式匹配的行:
grep –e “^yes” myfile
# 查找时不区分大小写:
grep –i “yes” myfile
# 查找匹配的行数:
grep -c “yes” myfile
# 从文件内容查找不匹配指定字符串的行:
grep –v “yes” myfile
# 从根目录开始查找所有扩展名为.log的文本文件,并找出包含”ERROR”的行
find / -type f -name*.log” | xargs grep “ERROR”
# 从当前目录开始查找所有扩展名为.in的文本文件,并找出包含”thermcontact”的行
find . -name*.in” | xargs grep “thermcontact”

awk

  • awk是逐行处理的,逐行处理的意思就是说,当awk处理一个文本时,会一行一行进行处理,处理完当前行,再处理下一行,awk默认以”换行符”为标记,识别每一行,也就是说,awk跟我们人类一样,每次遇到”回车换行”,就认为是当前行的结束,新的一行的开始,awk会按照用户指定的分割符去分割当前行,如果没有指定分割符,默认使用空格作为分隔符。

awk内建变量

变量

  • $0 当前记录(这个变量中存放着整个行的内容)
  • $1~$n 当前记录的第n个字段,字段间由FS分隔
  • FS 输入字段分隔符 默认是空格或Tab
  • NF 当前记录中的字段个数,就是有多少列,如果加上$符号,即$NF,表示一行的最后一个字段
  • NR 已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。
  • FNR 当前记录数,与NR不同的是,这个值会是各个文件自己的行号
  • RS 输入的记录分隔符, 默认为换行符
  • OFS 输出字段分隔符, 默认也是空格
  • ORS 输出的记录分隔符,默认为换行符
  • FILENAME 当前输入文件的名字
  • ARGC 命令行参数个数
  • ARGV 命令行参数排列
  • ENVIRON 支持队列中系统环境变量的使用
  • BEGIN{这里面放的是执行前的语句}
  • END{这里面放的是处理完所有的行后要执行的语句}
  • {这里面放的是处理每一行时要执行的语句}

常用命令

awk '{print $1, $3}' netstat.txt
awk '{printf "%-8s %-8s %-18s %-22s %-15s\n",$1,$3,$4,$5,$6}' netstat.txt
awk '$3 == 0 && $6 == "LAST_ACK"' netstat.txt
awk '$3 > 0 && NR != 1 {print $3}' netstat.txt
awk 'BEGIN{FS=":"} {print $1,$3,$6}' semi_colon_FS #awk -F: '{print $1,$3,$6}' semi_colon_FS
awk '/WAIT/' netstat.txt
awk 'NR != 1 {print > $6}' netstat.txt
awk 'NR!=1{a[$6]++;} END {for (i in a) print i ", " a[i];}' netstat.txt
cat shuf.txt | awk 'BEGIN{srand()} {print rand() "\t" $0}' | sort -n | cut -f2- #shuffle一个文件
awk 'BEGIN{FS="  "}{if ($1 == "payment") {print;}}' /data/log/maui.data.log #抽出所有第一个字段是payment的行
awk 'BEGIN{FS="  "}$1 == "payment"' /data/log/maui.data.log #或者ACTION部分不要用花括号圈引,则自动打印符合条件的相应行
awk '$2 == "beat"{print $3}' /data/log/budweiser.data.log | sort | uniq -c #取出第二列等于beat的行的第3列,然后统计出现的数量
awk '$2 == "beat"{print $3}' logfile | sort | uniq -c
awk '{ print length, $0 }' file | sort -nrk1 -s | cut -d" " -f2- #根据文件的每行长度进行排序

字符串切割

test="cluster	8	平使用面积|你有啥用|使用多少?|使用年限|使用率|使用率多少|套内使用面积|车位使用权多久"
echo $test | awk '{split($3,a,"|");for(i in a) print $2"\t"a[i]}' > out.txt
# split用法,将真个字符串按照:切割,结果存入数组a中
echo "12:34:56" | awk '{split($0,a,":" ); print a[1]}'     # 输出 12

echo "123" | awk '{print length}' # 字符串长度
awk -F ',' '{print substr($3,6)}'  # 表示是从第3个字段里的第6个字符开始,一直到设定的分隔符","结束.
substr($3,10,8)  # 表示是从第3个字段里的第10个字符开始,截取8个字符结束.
substr($3,6)     # 表示是从第3个字段里的第6个字符开始,一直到结尾
awk '$0 ~ /abc/ {gsub("a\d+c", "def", $0); print $1, $3}' abc.txt # 字符串替换,正则模式

正则表达式

【2023-1-10】awk的正则表达式,是属于:扩展的正则表达式(Extended Regular Expression 又叫 Extended RegEx 简称 EREs),详见

awk 常见调用正则表达式方法:

  • ① 直接过滤:使用
  • ② if条件判断
  • ③ 正则函数
# ① 
awk '/REG/{action}'
# /REG/为正则表达式,可以将$0中,满足条件记录 送入到:action进行处理.
# ② 
# awk正则运算语句(~,~!等同!~)
awk 'BEGIN{info="this is a test";if( info ~ /test/){print "ok"}}'
# ok

#1:仿豆瓣电影微信小程序 
# https://github.com/zce/weapp-demo
#
#2:微信小程序移动端商城 
# https://github.com/liuxuanqiang/wechat-weapp-mall

# 两行合并,剔除空格
cat m.txt | awk '{if($1~/^[1-9]+/){gsub("\s+","",$1);printf "["$1"]"}else{gsub("\s+","",$1);if($1)printf "("$1")\n"}}'

③ awk内置使用正则表达式函数

  • gsub( Ere, Repl, [ In ] ) 原地替换
  • sub( Ere, Repl, [ In ] )
  • match( String, Ere )
  • split( String, A, [ Ere] )
# 将tab替换为 |
cat a.txt | awk -F'\t' '{gsub("\t"," | ",$0);print "|"$0"|"}'

详细函数使用,可以参照:linux awk 内置函数详细介绍(实例)

多路输出

  • 代码:
    【2019-06-04】awk多路输出: 
    head data_ivr_20190401_20190430.txt | awk -F'\t' '{if($2~/01:/){print $0>>"tmp_a.txt"}else{print $0>>"tmp_b.txt"}}'
    head data_ivr_20190401_20190430.txt | awk -F'\t' 'BEGIN{a="tmp_a.txt";b="tmp_b.txt";system(">"a";>"b);}{if($2~/01:/){print $0>>a}else{print $0>>b}}'
    # 文件分流示例
    train_file='a.txt'
    test_file='b.txt'
    [ -e $train_file ] && > $train_file;
    [ -e $test_file ] && > $test_file;
    less error.txt | awk -v a=$train_file -v b=$test_file 'BEGIN {srand();OFS="\t";N=20} {r=int(rand()*N); if(r%N==1)print $0>>a; else{print $0>>b}}'
    less error.txt | awk 'BEGIN {srand();OFS="\t"} {print $0,rand()*1000}' |sort -k2nr -k5n|awk 'BEGIN {OFS="\t"} {print}'
    

输出特殊字符

【2022-12-21】输出单引号、双引号

测试数据:

1.1 微客主页-个人主页
1.2 微客主页-热门资讯
1.3 微客主页-热门房源
1.4 微客主页-热门楼盘
1.6 获客宝主页-我的微店-个人主页
1.7 获客宝主页-我的微店-单个二手房
1.8 获客宝主页-我的微店-单个新房
2.1 二手房-详情页
2.2 二手房-列表页
2.3 二手房-详情页cpt
3.1 新房-楼盘详情页
3.2 新房-户型详情页
3.3 新房-项目资料详情页
3.4 新房-列表页
3.5 新房-详情页cpt
4.1 获客宝主页-资讯详情页(gid)
4.2 获客宝主页-获客资讯模块(gid)
4.3 获客宝主页-今日必推模块(gid)
4.4 获客宝主页-资讯详情页(h5)
4.5 获客宝主页-获客资讯模块(h5)
4.6 获客宝主页-今日必推模块(h5)
5.1 霸屏海报-海报
6.1 租赁-详情页
6.2 租赁-列表页
7.1 拉新工具-h5拉新链接
7.2 拉新工具-拉新海报
8.1 首页-好房分享-单个二手房
8.2 首页-好房分享-单个新房
8.3 首页-获客资讯(gid)
8.4 首页-获客资讯(h5)
9.9 幸福楼市
10.1 RGC经纪人

awk 命令

  • 双引号:只需使用 \ 即可
  • 单引号:需要额外用单引号围起来,再加转义符
# 双引号:
awk '{print "\""}'    # 放大:awk '{print "  \"  "}'
# 使用“”双引号把一个双引号括起来,然后用转义字符\对双引号进行转义,输出双引号。
# 单引号:
awk '{print "'\''"}'  # 放大: awk '{print  "  '  \  '  '   " }'
# 使用一个双引号“”,然后在双引号里面加入两个单引号‘’,接着在两个单引号里面加入一个转义的单引号\',输出单引号。

cat share.txt | awk '{print "when substr(source_from,1,3)='\''" $1 "'\'' then '\''" $2"'\''"}'
# when substr(source_from,1,3)='1.1' then '微客主页-个人主页'
# when substr(source_from,1,3)='1.2' then '微客主页-热门资讯'
# when substr(source_from,1,3)='1.3' then '微客主页-热门房源'
# when substr(source_from,1,3)='1.4' then '微客主页-热门楼盘'

sed

  • sed是一款优秀的文本处理程序,但是其不同版本在用法和参数上存在着较大差异,建议大家在使用时一定要查询相关文档,以免出错。
# 删除只有空白字符行
sed -i -e '/^\s\+$/d' file #GNU

sed '1!G;h;$!d' pets.txt #反转一个文件的行
sed 'N; s/\n  /, /' pets.txt #将两行合并,并用逗号分开
sed -e 's/'$(echo -e "\x15")'//g' file # sed用于去除ascii不可打印的控制字符

# 常规的替换功能
sed "s/my/Hao Chen's/" pets.txt
sed "3s/my/your/" pets.txt
sed "3,6s/my/your/" pets.txt #只替换第3到第6行的文本

sed -n '/cat/,/fish/p' pets.txt # 只打印匹配cat和fish之前的行,-n表示不输出那些未匹配的行

sed -e 3,6{ -e /This/d -e } pets.txt
sed '3,6{/This/d;}' pets.txt #BSD sed, must add semi-colon

sed '3,6 {/This/{/fish/d;};}' pets.txt
sed '1,${/This/d;s/^ *//g;}' pets.txt
sed -E '/dog/{N;N;N;s/(^|\n)/&# /g;}' pets.txt #BSD sed
sed '/dog/,+3s/^/# /g' pets.txt #GNU sed
sed = pets.txt | sed 'N;s/\n/'$'\t''/' > line_num_pets.txt
sed = my.txt | sed 'N; s/^/    /; s/\(.\{5,\}\)\n/\1 /' #对文件中的所有行编号(行号在左,文字左端对齐)。
#sed = my.txt | sed -E 'N; s/(.*)\n/    \1 /' #貌似,我也可以这么写
sed '/'"$name"'/,/};/d' back_slash.txt
#实际应用,用来做代码重构时:(err) -> next err  =>   next
find . -name '*.coffee' | xargs sed -i '' -E '/\(err\) ->/{N;s/\(err\) ->\n +next err/next/g;}'
#在多行的时候,[[:space:]]才会匹配换行符\n,只有单行的时候,字符串尾部的换行符已经被sed截掉,所以匹配不到

#使用GNU sed去掉首行的BOM
find . -name '*.srt' -exec gsed -i -e '1s/^\xEF\xBB\xBF//' {} \;

gsed '1~2G' -i *.txt #奇数行后加空行
gsed -i -e '$a\' file #如果文件末尾没有换行符,则自动添加,解决"No newline at end of file"的问题,OSX sed中:sed -i '' -e '$a\'

# 在第1行插入内容
sed "1i\\"$'\n'"my monkey's name is wukong"$'\n' my.txt
# 在最后一行追加内容
sed "$ a \\"$'\n'"my monkey's name is wukong"$'\n' my.txt
# 在匹配行之前插入内容
sed "/fish/i\\"$'\n'"my monkey's name is wukong"$'\n' my.txt
# 在指定行修改为特定内容
sed "2c \\"$'\n'"my monkey's name is wukong"$'\n' my.txt

# 匹配行后追加内容
gsed -i '/matchpattern/a content' tmp.txt
# 如果content是以空白字符开头,比如tab或者空格,则用一个反斜线标记出来
gsed -i '/matchpattern/a \ content' tmp.txt
# 命令行中输入tab键:先按ctrl+v,然后再按tab
gsed -i '/matchpattern/a \	content' tmp.txt
# 匹配行后追加多行内容,可以把需要追加的内容写到一个文件中,然后使用r命令
gsed -i '/abcd/r otherfile.txt' tmp.txt

# 删除匹配行之前的内容,也就是保留匹配行之后的所有内容
sed -i '/^package /,$!d' *.go
# 显示一定范围内的行
sed -n '190,200p' tmp.txt

【2024-4-10】生成连续数字、字符串

# -f 或 --format=FORMAT:使用printf风格的浮点格式(使用%g或者%f占位,可指定宽度),可自定义格式输出序列。例如:
seq -f "ID-%05g" 3
# ID-00001
# ID-00002
# ID-00003

# -s 或 --separator=STRING:使用STRING分隔数字(默认值:\n)。例如:~$ 
seq -s , 3
# 1,2,3

# -w 或 --equal-width:使用前导零填充以均衡宽度。例如:~$ 
seq -w 8 10
# 08
# 09
# 10

grep

  • 待补充

shell案例

自定义通用函数

common.sh 包含的函数:

  • 打日志
  • 发邮件
  • 发短信
  • 多功能等待
#!/bin/bash
# -*- coding: utf-8 -*-
 
#===============================================================================
#            File:  common.sh
#           Usage:  sh common.sh
#     Description:  定义了常用函数
#    LastModified:  6/12/2012 16:28 PM CST
#         Created:  1/4/2012 11:06 AM CST
# 
#          AUTHOR:  wangqiwen(wangqiwen@*.com)
#         COMPANY:  baidu Inc
#         VERSION:  1.0
#            NOTE:  
#           input:  
#          output:  
#===============================================================================
 
ulimit -c unlimited

#init_log <log_file>
init_log()#清空log文件打一条日志
{
    >$LogFile # conf/var.sh中定义
    log "====================================="
    log "LogFile=$LogFile"
    log "\t\tshort-cut-pretreat"
    local day=`date "+%Y-%m-%d %H:%M:%S"`
    log "\t\t$day"
    log "====================================="
    declare -i log_count=0 # 整型局部变量
}
 
log()
{
    let log_count++
    echo -e "\n--------------------------[$log_count]th log--------------------------------\n"
    echo -e `date "+%Y-%m-%d %H:%M:%S"`"\t$@"
}

#read_mail <mail.conf> 结果在mail_receivers变量中
read_alarm_mail(){
    declare -i flag=0 # 整型局部变量
    if [ -f "$1" ];then
        cut -d "#" -f 1 "$1" |grep -v "^$" >tmp.$$
        while read line; do
            if [ $flag -eq 0 ];then
                mail_receivers=$line # 整型全局变量
                flag=1
            else
                mail_receivers="$mail_receivers,$line"
            fi
        done < tmp.$$
        rm -f tmp.$$
    else
        #设定一个默认值
        mail_receivers="-"
    fi
    echo "报警邮件接收人:$mail_receivers"
}
 
#向指定的邮件发送邮件告警
#$1:    告警主题
#$2:    需要被告警的详细内容
send_alarm_mail( )
{
    if [ $# -ne 2 ]; then
        return -1
    fi
    # 获取收件人列表
    echo "$2" | mail -s "$1" "$mail_receivers"
    if [ $? -ne 0 ]; then
        log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] 发送到 $mail_receivers 的邮件($1)失败!"
    else
        log "[`date "+%Y-%m-%d %H:%M:%S"`] [NOTE] 成功将邮件($1---$2) 发送至 $mail_receivers !"
    fi
    return 0
}
#对所有手机报警
#$1:    报警内容
send_list_msgs()
{ # 传入两个参数: $1 --> 报警短信内容  $n (n>=2) --> 报警接收人的手机号
    if [ $# -lt 2 ]; then
        echo "短信报警函数send_list_msgs参数不够!"
        return -1
    fi
    local i="-"
    local phone_number="-"
    for((i=2;i<=$#;i++))
    do
        eval "phone_number=\${$i}"
        # 向$phone_number发送报警信息$1
        gsmsend -s $GSMSERVER1:$GSMPORT1 -s $GSMSERVER2:$GSMPORT2 *$GSMPIORITY*"$phone_number@$1"
        if [ $? -ne 0 ];then
            log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] 报警短信发送失败 !"
        else
            log "[`date "+%Y-%m-%d %H:%M:%S"`] [NOTE] 报警信息 ($1) 成功发送到 $phone_number !"
        fi 
    done
}
#------------------------------------------------------
# 2012-2-25 PM 22:33   wangqiwen@*.com
# input: 每次等5min
# type=1 检测多个目录,各目录tag文件命名规则一致,且都在上一级目录
#(1) wait_hadoop_file   hadoop  time  1 tag_file   hadoop_dir1   hadoop_dir2   hadoop_dir3  
# 公用tag文件名(非绝对路径,tag文件默认在目录的上一层)
# 示例:wait_hadoop_file $hadoop 10   1  done        /a/b/c        /a/m/c       /a/m/d
# 或:  wait_hadoop_file $hadoop 10   1  finish.txt  /a/b/c        /a/m/c       /a/m/d
#   目录/a/b/c,/a/m/c,/a/m/d
#   若tag文件分别为:/a/b/c.done和/a/m/c.done和/a/m/d.done,那么,tag_file="done"
#   否则,所有目录的tag文件名相同,tag_file可自定义为某文件名
#
# type=2 检测多个目录,各目录的tag文件路径无规则,需直接指定其完整路径
#(2) wait_hadoop_file hadoop    time   2  hadoop_dir1 tag_file1  hadoop_dir2 tag_file2  # 私用tag文件名,绝对路径
# 示例:wait_hadoop_file $hadoop 10   2  /a/b/c      /a/b/c.done /a/m/c     /a/m/c.txt   /a/m/d     /a/m_d.txt 
# 目录与tag文件要依次成对出现
#
# type=3 检测多个集群文件(非目录),无须tag文件
#(3) wait_hadoop_file    hadoop    time   3 hadoop_file1    hadoop_file2   hadoop_file3   # 判断多个hadoop文件的存在性
# 示例:wait_hadoop_file $hadoop    10    3     /a/b/file1.txt  /a/b/file2.txt  /a/m/file3.log 
#
# output: 通过变量FILE_READY记录,返回状态值0~3 
#   0 : 检查完毕,或成功删除临时目录
#   1 : 参数输入有误:个数、奇偶性不对。type=2时要保证tag文件与目录依次成对出现
#   2 : 超时,停止检查
#   3 : _temporary目录删除失败,但不影响hadoop任务,属于成功状态
#   成功状态值: 0 和 3
#------------------------------------------------------
#shopt -s -o nounset  # 变量声明才能使用
wait_hadoop_file(){
    local FILE_READY=1 # 检查结果
    local ARG_NUM=$# # 参数个数
    [ $ARG_NUM -lt 4 ] && {  echo -e "input error ! $ARG_NUM < 4 , please check !\t参数有误,小于4个,请确认!";return $FILE_READY;} 
    local HADOOP=$1 # Hadoop集群客户端地址
    local HADOOP_CHECK_PATH="/" # 待检查的集群目录或文件
    local HADOOP_TEMP_PATH="/" # _temporary目录
    local HADOOP_CHECK_TAG="/"  # 待检查的tag文件
    local HADOOP_WATI_TIME=$2 # 最长等待时间(单位:分钟)
    local CURRENT_ERRORTIME=1 # 等待时间
    local CURRENT_PATH=1  # 当前遍历目录数
    local PATH_INDEX=0  # 当前检测目录所在的参数位置
    local TAG_INDEX=0  # 当前检测tag文件所在的参数位置
    local TAG_NAME="/"
    local DIR_NUM=0  # 待检测目录数
    local TYPE=$3  # type
    # 根据类型分别处理
    # type=1,2时,输入参数至少5个;type=3时,输入参数至少4个
    case $TYPE in
    1) 
        [ $ARG_NUM -eq 4 ] && {  echo -e "input error ! $ARG_NUM < 5 , please check !\t参数有误,小于5个,请确认!";return $FILE_READY;}
        DIR_NUM=$(($ARG_NUM-4));;  # 待检测的目录数
    2)
        [ $ARG_NUM -eq 4 ] && {  echo -e "input error ! $ARG_NUM < 5 , please check !\t参数有误,小于5个,请确认!";return $FILE_READY;}
        [ $((($ARG_NUM-3)%2)) -ne 0 ] && { FILE_READY=1; echo -e "Input error ! Dirs miss to match tags in pairs ...";return $FILE_READY;} # 目录和tag文件不成对,退出case
        DIR_NUM=$((($ARG_NUM-3)/2));;  # 待检测的目录数
    3)  
        DIR_NUM=$(($ARG_NUM-3));; # 待检测文件数
    *)
        echo "Input error ! Type value $TYPE illegal... type参数值错误,1-3,非$TYPE"
        return $FILE_READY;;
    esac
 
    while [ $CURRENT_PATH -le $DIR_NUM ]
    do
        # path/tag参数位置获取
        if [ $TYPE -eq 1 ];then
            PATH_INDEX=$((4+$CURRENT_PATH))
            TAG_INDEX=4
            eval "HADOOP_CHECK_PATH=\${$PATH_INDEX}"  # 待检查的集群目录
            eval "TAG_NAME=\${$TAG_INDEX}"
            if [ "$TAG_NAME" == "done" ];then #tag文件在上一级目录
                HADOOP_CHECK_TAG="${HADOOP_CHECK_PATH%/*}/${HADOOP_CHECK_PATH##*/}.$TAG_NAME" # tag文件名为:上一级目录名字.done
            else
                HADOOP_CHECK_TAG="${HADOOP_CHECK_PATH%/*}/$TAG_NAME" # 上一级目录自定义tag文件名
            fi
        elif [ $TYPE -eq 2 ];then
            PATH_INDEX=$((4+2*($CURRENT_PATH-1))) 
            TAG_INDEX=$(($PATH_INDEX+1))
            eval "HADOOP_CHECK_PATH=\${$PATH_INDEX}"  # 待检查的集群目录
            eval "HADOOP_CHECK_TAG=\${$TAG_INDEX}" # tag文件的绝对路径
        else
            PATH_INDEX=$((3+$CURRENT_PATH))
            eval "HADOOP_CHECK_PATH=\${$PATH_INDEX}"  # 待检查的集群文件
        fi
        while [ $CURRENT_ERRORTIME -le $HADOOP_WATI_TIME ]
        do
            $HADOOP fs -test -e $HADOOP_CHECK_PATH  # 检测path目录是否存在
            if [ $? -eq 0 ];then # 目录存在
                break
            else  # 目录不存在,等待生成
                CURRENT_ERRORTIME=$(($CURRENT_ERRORTIME+1))
                date "+%Y-%m-%d %H:%M:%S"
                sleep 5m
            fi
        done
        [ $CURRENT_ERRORTIME -gt $HADOOP_WATI_TIME ] && { FILE_READY=2; echo -e "Time out when checking dirs[$HADOOP_CHECK_PATH] ...\t等待超时,目录未检查完毕!" ;break;}
        if [ $TYPE -eq  3 ];then
            CURRENT_PATH=$(($CURRENT_PATH+1)) # 检查下一个目录
        else
            HADOOP_TEMP_PATH="${HADOOP_CHECK_PATH}/_temporary" # 临时目录
            # 检测tag文件,并删除临时目录
            while [ $CURRENT_ERRORTIME -le $HADOOP_WATI_TIME ]
            do
                $HADOOP fs -test -e $HADOOP_CHECK_TAG # 检测tag文件是否存在
                if [ $? -eq 0 ];then  # tag文件存在  [2012-12-4]停止临时文件删除
                    #$HADOOP fs -test -e $HADOOP_TEMP_PATH  # 检查临时目录是否存在
                    #if [ $? -eq 0 ];then  # 临时目录存在
                    #   $HADOOP fs -rmr $HADOOP_TEMP_PATH  # 删除临时目录
                    #   [ $? -ne 0 ] && { FILE_READY=3; echo -e "Failed to delete temp dir[$HADOOP_TEMP_PATH]...\t临时目录删除失败";} #break 2; } # 删除失败,退出2重循环 [2012-2-23]不退出,继续检查
                    #fi
                    CURRENT_PATH=$(($CURRENT_PATH+1)) # 检查下一个目录
                    break
                else  # tag文件不存在,错误次数自增,循环等待
                    CURRENT_ERRORTIME=$(($CURRENT_ERRORTIME+1))
                    date "+%Y-%m-%d %H:%M:%S"
                    sleep 5m
                fi
            done
        fi
    done
    [ $CURRENT_PATH -gt $DIR_NUM ] && { [ $FILE_READY -ne 3 ] && FILE_READY=0;  echo "All dirs ready ! 目录检查完毕!";} # 所有目录都准备好,新增成功状态3,[2012-2-23]
    return $FILE_READY
}

循环任务

从某个日期开始,往前递推N天,并启动任务

[ $# -ge 1 ] && date=$1 || date=`date -d "1 days ago" +%Y%m%d`
readonly rp_hadoop="/home/work/bin/hadoop-client-rp-product/hadoop/bin/hadoop"
i=1;n=3
while [ $i -le $n ]
do
    d=`date -d "${i} days ago $date" +%Y%m%d`
    echo "[`date "+%Y-%m-%d %H:%M:%S"`] 第 $i 天 ($d)"
    sh start.sh $d
    [ $? -ne 0 ] && { echo "[`date "+%Y-%m-%d %H:%M:%S"`] 第 $i 天 ($d) oneday-pretreat 执行失败...";exit -1; } || { echo "[`date "+%Y-%m-%d %H:%M:%S"`] 第 $i 天( $d ) oneday-pretreat 执行成功...";  }
    cd ../../short-cut-pretreat-v2/navigation
    sh start.sh $d
    [ $? -ne 0 ] && { echo "[`date "+%Y-%m-%d %H:%M:%S"`] 第 $i 天 ($d) short-cut-pretreat 执行失败...";exit -1; } || { echo "[`date "+%Y-%m-%d %H:%M:%S"`] 第 $i 天( $d ) short-cut-pretreat 执行成功..."; ((i++)); }
    cd -
done

shell调度MapReduce任务

单次案例:

#!/bin/bash
# -*- coding: utf-8 -*-
 
#===============================================================================
#            File:  start_hadoop.sh [session]
#           Usage:  
#     Description:  pc端session日志的hadoop启动脚本 
#    LastModified:  2013-3-28 12:51 PM 
#         Created:  27/12/2012 11:17 AM CST
#          AUTHOR:  wangqiwen(wangqiwen@*.com)
#         COMPANY:  baidu Inc
#         VERSION:  1.0
#            NOTE:  
#           input:  
#          output:  
#===============================================================================
#:<<note
# online 
set -x
if [ $# -ne 9 ]; then
    echo "[$0] [ERROR] [`date "+%Y-%m-%d %H:%M:%S"`] [session] input error ! \$#=$# not 9"
    exit -1
fi
hadoop=$1;input=${2//;/ -input };output=$3
jobname=$4;main_dir=$5;date=$6;task_prefix=$7
map_con_num=$8;reduce_num=$9
#note

:<<note
# test
date="20130314"
jobname="session"
hadoop="/home/work/hadoop-rp-rd-nj/hadoop/bin/hadoop"
#input="/log/22307/nj_rpoffline_session_ting/$date/0000/szwg-ston-hdfs.dmop/0000"
input="/log/22307/nj_rpoffline_session_ting/$date/0000/szwg-ston-hdfs.dmop/0000/part-00000"
# baike http://cq01-test-nlp2.cq01.baidu.com:8130/table/view?table_id=347
output="/user/rp-rd/wqw/test/sm/$jobname/$date/0000"
map_con_num=200
reduce_num=200
main_dir="/home/work/wqw/sm-navi-data-cookie/new/bin"
note

echo "=========================$jobname start ============================="
done_file="${output%/*}/${output##*/}.done"
${hadoop} fs -test -e ${output} && ${hadoop} fs -rmr ${output}
${hadoop} fs -test -e ${done_file} && ${hadoop} fs -rm ${done_file}
echo "-------输出目录,标记文件清理完毕---------------------"
echo "[$0] [NOTE] [`date "+%Y-%m-%d %H:%M:%S"`] [$jobname] start to commit hadoop job"
${hadoop} streaming \
        -jobconf mapred.job.name="${task_prefix}" \
        -jobconf mapred.job.priority=HIGH \
        -jobconf mapred.task.timeout=600000 \
        -jobconf mapred.map.tasks=$map_con_num \
        -jobconf mapred.reduce.tasks=$reduce_num \
        -jobconf stream.memory.limit=1000 \
        -jobconf stream.num.map.output.key.fields=2 \
        -jobconf num.key.fields.for.partition=1 \
        -cacheArchive /user/rp-product/dcache/thirdparty/python2.7.tar.gz#py27 \
        -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner \
        -jobconf mapred.job.map.capacity=$map_con_num -jobconf mapred.job.reduce.capacity=$reduce_num \
        -jobconf mapred.map.max.attempts=10 -jobconf mapred.reduce.max.attempts=10 \
        -file $main_dir/hadoop_jobs/session/*.py \
        -file $main_dir/tools/hadoop_tool/py27.sh \
        -file $main_dir/tools/url_normalize/m \
        -cmdenv date_of_data=$date \
        -output ${output} \
        -input ${input} \
        -mapper "sh py27.sh mapper.py" \
        -reducer "./m"
 
if [ $? -ne 0 ]; then
    echo "[$0] [Error] [`date "+%Y-%m-%d %H:%M:%S"`] [$jobname] job failed !"
    exit -1
fi
#${hadoop} fs -touchz ${done_file}
 
echo "[$0] [NOTE] [`date "+%Y-%m-%d %H:%M:%S"`] [$jobname] hadoop job finished"
echo "=========================$jobname end ============================="
exit 0

多任务调度

#!/bin/bash
# -*- coding: utf-8 -*-
#
#===============================================================================
#            File:  start.sh [oneday-pretreat]
#           Usage:  
#                运行指定日期:    sh start.sh  20120104
#        正常运行(昨天):   sh start.sh
#        查看运行日志:    tail -f ../log/20120104/run_log_20120103.txt
#     Description: oneday-pretreat总控脚本
#    LastModified:  12/13/2012 15:29 AM CST
#         Created:  1/4/2012 11:16 AM CST
# 
#          AUTHOR:  wangqiwen(wangqiwen@*.com)
#         COMPANY:  baidu Inc
#         VERSION:  1.0
#            NOTE: 
#       input:  mergeOneDay和dump
#      output:  作为short-cut-pretreat的输入
#===============================================================================
 
#搭建执行环境
source ./build.sh
#读入路径、时间配置
if [ $# -eq 1 ];then
    date_check=`echo "$1" | sed -n '/^[0-9]\{8\}$/p'`
    if [ -z $date_check ];then
        # 如果字符串为空,表明输入参数不当
        echo "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] date of start.sh input error ... 启动脚本的日期参数错误!"
        exit -1
    else
        source $ConfDir/vars.sh $1
    fi
else
    source $ConfDir/vars.sh
fi
 
#####################for mini#############################
export HADOOP_HOME=${rp_hadoop%/*/*}
export JAVA_HOME=${HADOOP_HOME%/*}/java6
# build.sh中读入BinDir
source $BinDir/oneday_onlyUrl_mini/mini/output/jjpack2text/conf/dlb_env.sh
export PATH=$PATH:$HADOOP_HOME/bin
##########################################################
 
#是否开启调试模式
if [ $debug_on -eq 1 ];then
        set -x
else
    set +x
fi
 
#导入公共函数
source ./common.sh #增加./,防止与环境变量重名
#日志初始化
init_log  #common.sh中定义
 
#重定向标准输入和输出到指定文件
exec 3<>$LogFile  #LogFile在conf/var.sh中定义
exec 1>&3 2>&1
 
log "$alarm_module_info" #模块信息
log "[`date "+%Y-%m-%d %H:%M:%S"`] [NOTE] load conf file 加载配置文件..."
#读入邮件配置
read_alarm_mail $ConfDir/mail.conf # read_alarm_mail源于common.sh
#读入短信配置
source $ConfDir/msg_conf.sh
#=========================hadoop任务函数封装===================
check_jobs_input()
{  # 参数: job type name warning msg_receiver 外部依赖才有短信报警
    [ $# -lt 5 ] && { echo "check_jobs_input input ($@) error ! ($#>= 5)";exit -1; }
    local my_job=$1
    local job_name="${my_job}-$date"
    local my_type=$2
    local hadoop_file="-"
    case $my_type in
    1)  # 内部依赖,超时仅发送邮件
        eval "hadoop_file=\$${my_job}_input_tag_in";;
    2)  # 外部依赖,超时发报警短信\短信,另外增加预警
        eval "hadoop_file=\$${my_job}_input_tag_out";;
    *)
        echo "check_jobs_input input error ! type=[1,2]"
        exit -1;;
    esac
    eval "local wait_time=\$${my_job}_wait_time" # 2012-5-22
    local name=$3
    local warning=$4  # 是否需要预警
    local msg_receiver=$5
    for((i=6;i<=$#;i++))
    do
        eval "msg_receiver=\"\$msg_receiver \${$i}\""
    done
    # 外部依赖,先等待,超时发出预警,继续等待最后时间
    if [ $warning -ne 0 ];then
        # 数据生成较晚,提醒相关人员关注
        eval "local wait_time_notice=\$${my_job}_wait_time_notice"
        # 验证${my_job}中是否定义提醒时间
        [ -e $wait_time_notice ] && { log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] $job_name ---[check_jobs_input] 未定义的变量: ${my_job}_wait_time_notice ";exit -1; }    
        wait_hadoop_file $rp_hadoop $wait_time_notice 3 $hadoop_file # 等待文件ready  2012-5-22
        return_value=$?
        if [ $return_value -ne 0 ];then
            rest_time=$(echo $wait_time_notice | awk '{print $0/12.0}')
            log "[`date "+%Y-%m-%d %H:%M:%S"`] [WARNING] [返回值: $return_value] $job_name 上游数据 ($date,$name) 还没生成,存在风险...再等待 $rest_time 小时..... "
            [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [WARNING] $job_name 预警时间已到,请及时推进" "[`date "+%Y-%m-%d %H:%M:%S"`] [WARNING] [返回值:$return_value] $job_name 预警时间已到,上游数据 ($date,$name) 还未生成,存在风险,再等待 $rest_time 小时...请 [$alarm_module_RP_rd] 及时跟进, $alarm_module_info" # 邮件报警
            [  $alarm_msg_off -eq 0 -a $my_type -eq 2 ] && send_list_msgs  "[oneday-pretreat] [WARNING] [返回值]:$return_value] $job_name 预警时间已到,上游数据 ($date,$name) 还未生成,存在风险,再等待 $rest_time 小时,请 [$alarm_module_RP_rd] 及时跟进." $msg_receiver 
        else
            return 0  # 预警时间内,数据ready,函数退出
        fi
        # 最晚抢救时间已到,当天挖掘无法更新,进入自动补数据阶段
        eval "local wait_time_warning=\$${my_job}_wait_time_warning"
        # 验证${my_job}中是否定义预警时间
        [ -e $wait_time_warning ] && { log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] $job_name --- [check_jobs_input] 未定义的变量: ${my_job}_wait_time_warning ";exit -1; } 
        wait_hadoop_file $rp_hadoop $wait_time_warning 3 $hadoop_file # 等待文件ready  2012-5-22
        return_value=$?
        if [ $return_value -ne 0 ];then
            rest_time=$(echo $wait_time_warning | awk '{print $0/12.0}')
            log "[`date "+%Y-%m-%d %H:%M:%S"`] [WARNING] [返回值: $return_value ] $job_name 接口数据最晚等待时间已到! 上游数据 ($date,$name) 还没生成,当天挖掘赶不上,自动进入补数据阶段...再等待 $rest_time 小时...请及时处理."
            [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [WARNING] $job_name 预警时间已到,请及时推进..." "[`date "+%Y-%m-%d %H:%M:%S"`] [WARNING] [返回值:$return_value] $job_name 接口数据最晚等待时间已到 !上游数据 ($date,$name) 还未生成,当天挖掘赶不上,自动进入补数据阶段,再等待 $rest_time 小时...请 [$alalarm_module_RP_rd] 及时跟进,$alarm_module_info" # 邮件报警
            [  $alarm_msg_off -eq 0 -a $my_type -eq 2 ] && send_list_msgs  "[oneday-pretreat] [WARNING] [返回值 $return_value ] $job_name 接口数据最晚等待时间已到 !,上游数据 ($date,$name) 还未生成,当天挖掘赶不上,自动进入补数据阶段,再等待 $rest_time 小时,请[ $alarm_module_RP_rd ]及时跟进." $msg_receiver
        else
            return 0   # 补数据成功,程序返回  
        fi
    fi
    # 补数据超时失败,程序退出
    wait_hadoop_file $rp_hadoop $wait_time 3 $hadoop_file # 等待文件ready  2012-5-22
    return_value=$?
    if [ $return_value -ne 0 ];then
        log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] [返回值: $return_value] $job_name 等待超时 ! 上游数据 ($date,$name) 未按时生成..."  # 打运行日志
        [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [ERROR] $job_name 等待超时!" " [`date "+%Y-%m-%d %H:%M:%S"`][ERROR][返回值: $return_value] $job_name 失败! 上游数据 ($date,$name) 未按时生成..请联系 [$alarm_module_RP_rd] 处理,$alarm_module_info" # 邮件报警
        [  $alarm_msg_off -eq 0 -a $my_type -eq 2 ] && send_list_msgs  "[oneday-pretreat] [ERROR] [返回值: $return_value] $job_name 等待超时!上游数据 ($date,$name) 未按时生成...请 [$alarm_module_RP_rd] 处理,$alarm_module_info" $msg_receiver # 短信报警 
        exit -1 # 超时等待,退出
    fi
}
 
hadoop_dir_search()
{   # 参数: job_name type  name 
    # 仅用于接口数据的自动查找,替换
    [ $# -lt 3 ] && echo "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] hadoop_dir_search input error ! ($# >= 3)"
    local job_name=$1
    local my_type=$2 # 区分正常产出数据(type=1)和转储日志(type非1)
    local name=$3
    local tmp_date='-'
    local tmp_path='-'
    local tmp_tag='-'
    eval "local days=\$${name}_findDays"
    eval "wait_hadoop_file \$rp_hadoop \$${name}_wait_time 3 \$${name}_tag"
    return_value=$?
    if [ $return_value -ne 0 ];then
        log "[`date "+%Y-%m-%d %H:%M:%S"`] [WARNING] [$job_name] 当天目录 ${name}_dir/$date/0000.done 不存在,请核实 !"
        [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [WARNING] 上游数据 $name($date) 不存在!" "[`date "+%Y-%m-%d %H:%M:%S"`] [WARNING] 上游数据 $name($date) 不存在...请联系 [$alarm_module_RP_rd] 追查原因,$alarm_module_info"
        # 当天数据超时未生成,common_query 需要自动搜索最近的数据
        for((i=1;i<=$days;i++))
        do
            tmp_date=`date -d "-$i day $date" +%Y%m%d` # 获取前i天日期
            if [ $my_type -eq 1 ];then
                eval "tmp_path=\"\${${name}_dir%/*/*}/$tmp_date/0000\"" # 拼接临时目录----正常产出数据
                eval "tmp_tag=\"\${${name}_dir%/*/*}/$tmp_date/0000.done\"" # 拼接临时目录----正常产出数据
            else
                eval "tmp_path=\"\${${name}_dir%/*/*/*}/$tmp_date/0000/0000\"" # 拼接临时目录----转储日志
                eval "tmp_tag=\"\${${name}_dir%/*/*/*}/$tmp_date/0000/@manifest\"" # 拼接临时目录----转储日志
            fi
            $rp_hadoop fs -test -e $tmp_tag  # 检查默认的目录标记文件 [20130218]
            if [ $? -eq 0 ];then
                eval "${name}_dir=\"$tmp_path\""  # 更改原目录值
                eval "${name}_tag=\"$tmp_tag\""   # 更改原标记文件
                break
            else
                log "[`date "+%Y-%m-%d %H:%M:%S"`] [WARNING] [$job_name] $tmp_tag 不存在,请核实 !"
                [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [WARNING] 上游数据 $name($tmp_date) 不存在!" "[`date "+%Y-%m-%d %H:%M:%S"`] [WARNING] 上游数据 $name($d) 不存在...请联系 [$alarm_module_RP_rd] 追查原因,$alarm_module_info"
            fi
        done
        [ $i -ge $days ] && exit -1
    fi
}
start_hadoop_jobs()
{  # 参数: jobname arg1 arg2 ...  不包括基本参数
    [ $# -lt 1 ] && echo "start_hadoop_jobs input error ! ($# >= 1)"
    local myjob=$1
    local myjob_name="${1}-$date"
    local args=""
    if [ $# -ge 2 ];then
        # 获取额外参数
        for((i=2;i<=$#;i++))
        do
            eval "args=\"$args \${$i}\""
        done
    fi
    #启动任务
    eval "cd \$${myjob}_local_dir"
    eval "\$shellBin start_hadoop.sh \$rp_hadoop \$${myjob}_input \$${myjob}_output \$myjob_name \$${myjob}_map_con_num \$${myjob}_reduce_num \$args"
    return_value=$?
    if [ $return_value -eq 0 ];then
        log "[`date "+%Y-%m-%d %H:%M:%S"`] [NOTE] [返回值: $return_value] $myjob_name finished ! $myjob_name 执行成功..."
        eval output=\$${myjob}_output
        ${rp_hadoop} cr -register ${output} -cronID $cronID # zk注册数据状态
        if [ $? -ne 0 ];then
            log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] $myjob_name zk状态注册失败.."
            [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [ERROR] $myjob_name zk状态注册失败.."  " [`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] $myjob_name zk状态注册失败...请联系 $alarm_module_stra_rd 处理,谢谢! $alarm_module_info"
        fi
    else
        log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] [返回值: $return_value] $myjob_name failed ! $myjob_name 执行失败..."
        [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [ERROR] $myjob_name Failed !"  " [`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] $myjob_name failed when running ! $myjob_name 执行过程中失败...请联系 $alarm_module_stra_rd 处理,谢谢! $alarm_module_info"
        exit -1
    fi
    cd -
}
 
#=======================启动子任务==============================
 
#---------------oneday_onlyUrl----------------------
#  作用: 抽取URL,去重,方便下一步的网页库查询           |
#  输入: mergeOneDay和database_dump                    |
#  输出: oneday_onlyUrl                                       |
#-------------------------------------------------------
jobname1="oneday_onlyUrl"
oneday_onlyUrl()
{
    # 等待接口数据
    check_jobs_input $jobname1 2  "mergeOneDay&database_dump" 1 $MOBILE_LIST_ALL # 外部依赖,需要预警
    #从集群上下载记录mergeOneDay信息的xml文件
    local_xml_mergeOneDay_file="${DataDir}/${mergeOneDay_tag##*/}" #提取文件名
    [ -e $local_xml_mergeOneDay_file ] && rm $local_xml_mergeOneDay_file # 删除已有文件
    sleep 5 # 等待5S,防止原xml文件不完整
    $rp_hadoop fs -copyToLocal $mergeOneDay_tag $DataDir #复制xml文件到本地
    cd $CheckDir # 2012-12-11
    $pythonBin mergeOneDay_check.py $local_xml_mergeOneDay_file #检验mergeOneDay数据源是否满足要求
    cd - # 2012-12-11
    return_value=$?
    if [ $return_value -ne 0 ];then
        log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] [返回值:$return_value] $jobname1 失败!上游数据 mergeOneDay-$date 缺失过多,不满足要求..."
        [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [ERROR] [返回值:$return_value] $jobname1 失败!" " [`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] $jobname1 失败!上游数据 mergeOneDay-$date 数据源缺失过多,不满足要求...请联系 [$alarm_module_RP_rd] 追查原因,$alarm_module_info"
        [  $alarm_msg_off -eq 0 ] && send_list_msgs  "[oneday-pretreat] [ERROR] [返回值:$return_value] $jobname1 失败!上游数据 mergeOneDay-$date 数据源缺失过多,不满足要求...请 [$alarm_module_RP_rd] 追查原因,谢谢! $alarm_module_info" $MOBILE_LIST_ALL
        exit -1
    fi
    $rp_hadoop fs -touchz $mergeOneDay_ok # 数据验证通过,创建标记文件,通知下游模块启动 
    start_hadoop_jobs "oneday_onlyUrl"
}
if [ $oneday_onlyUrl_pass -eq 0 ];then
    oneday_onlyUrl
    $rp_hadoop fs -touchz $mergeOneDay_ok # 数据验证通过,创建标记文件,通知下游模块启动 
    ${rp_hadoop} cr -register $mergeOneDay_ok -cronID $cronID # 注册数据状态
    if [ $? -ne 0 ];then
        log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] mergeOneDay_ok zk状态注册失败.."
        [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [ERROR] mergeOneDay_ok zk状态注册失败.."  " [`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] mergeOneDay_ok zk状态注册失败...请联系 $alarm_module_stra_rd 处理,谢谢! $alarm_module_info"
        fi
fi
 
#-----------oneday_onlyUrl_mini-------------------------
#  作用: mini网页库查询                                |
#  输入: oneday_onlyUrl                                |
#  输出: oneday_onlyUrl_mini                           |
#-------------------------------------------------------
jobname2="oneday_onlyUrl_mini"
oneday_onlyUrl_mini()
{
     
    check_jobs_input $jobname2 1  "oneday_onlyUrl" 0 $MOBILE_LIST_ALL # 内部依赖
    cd $oneday_onlyUrl_mini_local_dir/mini/output
    # ----------通过vars.sh中的路径配置,python程序解析修改test.xml
    less $ConfDir/oneday_onlyUrl_mini_conf.xml | awk -v input=$oneday_onlyUrl_mini_hdfs_input -v output=$oneday_onlyUrl_mini_hdfs_output -v bin_path="$oneday_onlyUrl_mini_bin_path" '{
        if($0~/<hdfs_input>.*<\/hdfs_input>/)
            print "\t\t<hdfs_input>"input"</hdfs_input>"
        else if($0~/<hdfs_output>.*<\/hdfs_output>/)
            print "\t\t<hdfs_output>"output"</hdfs_output>"
        else if($0~/<bin_path>.*<\/bin_path>/)
            print "\t\t<bin_path>"bin_path"</bin_path>"
        else
            print $0
    }' > test.xml 
    echo "=======================mini网页库========================="
    $pythonBin Xml2stream.py test.xml  # 启动网页库查询,后台操作
    ${rp_hadoop} fs -test -e "${oneday_onlyUrl_mini_output}/part-00999" # part-00999存在才证明mini查询成功
    if [ $? -eq 0 ];then
        # 任务执行完毕后,创建便于检测的tag文件
        $rp_hadoop fs -touchz "${oneday_onlyUrl_mini_output%/*}/${oneday_onlyUrl_mini_output##*/}.done"
        ${rp_hadoop} cr -register ${oneday_onlyUrl_mini_output} -cronID $cronID # 注册数据状态
        if [ $? -ne 0 ];then
            log "[`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] oneday_onlyUrl_mini zk状态注册失败.."
            [  $alarm_mail_off -eq 0 ] && send_alarm_mail "[oneday-pretreat] [ERROR] oneday_onlyUrl_mini zk状态注册失败.."  " [`date "+%Y-%m-%d %H:%M:%S"`] [ERROR] oneday_onlyUrl_mini zk状态注册失败...请联系 $alarm_module_stra_rd 处理,谢谢! $alarm_module_info"
        fi
    fi
    # 删除多余的临时目录
    echo "----清除临时文件 $oneday_onlyUrl_mini_hdfs_tmp ------"
    #exec 1>/dev/null 2>&1 # 将以下删除信息扔到位桶里
    for dir in $oneday_onlyUrl_mini_hdfs_tmp
    do
 
        echo "删除临时目录:${oneday_onlyUrl_mini_output%/*}/$dir"
        $rp_hadoop fs -rmr "${oneday_onlyUrl_mini_output%/*}/$dir" &>/dev/null
    done
    #exec 1>&3 2>&1  # 恢复重定向到日志文件
    echo "========================结束============================"
    cd -
    log "[`date "+%Y-%m-%d %H:%M:%S"`] [NOTE] $jobname2 百灵库查询完毕..."
}
 
if [ $oneday_onlyUrl_mini_pass -eq 0 ];then
    oneday_onlyUrl_mini 
fi
#------------oneday_baiduid---------------------------------
#  作用:                                               |
#  输入: mergeOneDay和baiduid                          |
#  输出: oneday_baiduid                                |
#-------------------------------------------------------
jobname3="oneday_baiduid"
#-----------后台等待baiduid---------------------
oneday_baiduid(){
    check_jobs_input $jobname3 2  "mergeOneDay" 0 $MOBILE_LIST_ALL # 外部依赖
    start_hadoop_jobs $jobname3 $oneday_baiduid_max_freq $oneday_baiduid_min_time 
}
 
if [ $oneday_baiduid_pass -eq 0 ];then
    oneday_baiduid &  # 后台运行
fi
 
#------------oneday_onlyUrl_mini_plus------------------
#  作用: 网页库查询结果上附加unifyUrl和commonQuery     |
#  输入: oneday_onlyUrl_mini,urlunify,commonQuery      |
#  输出: oneday_onlyUrl_mini_plus                      |
#-------------------------------------------------------
jobname4="oneday_onlyUrl_mini_plus"
oneday_onlyUrl_mini_plus(){
    hadoop_dir_search $jobname4 1 "common_query" # 检查并查找可替代的commonQuery数据  统计产出(2013-5-15)
    hadoop_dir_search $jobname4 1 "oneday_onlyUrl_mini" # 检查并查找可替代的mini数据  原始日志
    #check_jobs_input $jobname4 1  "oneday_onlyUrl_mini" 0 $MOBILE_LIST_STRA  # 内部
    oneday_onlyUrl_mini_plus_input="$urlunify_dir $common_query_dir $oneday_onlyUrl_mini_dir"
    start_hadoop_jobs $jobname4 $oneday_onlyUrl_mini_plus_input_str1 $oneday_onlyUrl_mini_plus_input_str2 $oneday_onlyUrl_mini_plus_input_str3
    reset_common_query # 恢复common_query原始值
}
if [ $oneday_onlyUrl_mini_plus_pass -eq 0 ];then
    oneday_onlyUrl_mini_plus
fi
 
#------------oneday_baiduid_plus----------------------------
#  作用: oneday_baiduid附加URL信息                         |
#  输入: oneday_baiduid和oneday_onlyUrl_mini               |
#  输出: oneday_baiduid_plus                               |
#-------------------------------------------------------
jobname5="oneday_baiduid_plus"
oneday_baiduid_plus()
{
    check_jobs_input $jobname5 1  "oneday_baiduid|oneday_onlyUrl_mini_plus" 0 $MOBILE_LIST_STRA # 内部依赖
    start_hadoop_jobs $jobname5 $oneday_baiduid_plus_input_str1 $oneday_baiduid_plus_input_str2
}
 
if [ $oneday_baiduid_plus_pass -eq 0 ];then
    oneday_baiduid_plus
fi
 
#------------oneday_uid_plus----------------------------
#  作用: baiduid映射成uid                              |
#  输入: baiduid-uid-mapping和oneday_baiduid_plus      |
#  输出: oneday_uid_plus                           |
#-------------------------------------------------------
jobname6="oneday_uid_plus"
#-----------后台等待baiduid-uid-mapping-------------------
oneday_uid_plus(){
    hadoop_dir_search $jobname6 1 "baiduid_uid_mapping" # 检查并查找可替代的baiduid-uid数据---程序产出
    check_jobs_input $jobname6 1  "oneday_baiduid_plus" 0 $MOBILE_LIST_STRA # 内部依赖
    oneday_uid_plus_input="${baiduid_uid_mapping_dir} ${oneday_baiduid_plus_output}"
    start_hadoop_jobs $jobname6  $oneday_uid_plus_input_str1 $oneday_uid_plus_input_str2 
    reset_baiduid_uid_mapping # 恢复baiduid_uid_mapping原始值
}
 
if [ $oneday_uid_plus_pass -eq 0 ];then
    oneday_uid_plus  & # 后台运行
fi
 
#------------oneday_activeBaiduid_plus------------------
#  作用: baiduid映射成uid                              |
#  输入: activeBaiduid和oneday_baiduid_plus           |
#  输出: oneday_activeBaiduid_plus                     |
#-------------------------------------------------------
jobname7="oneday_activeBaiduid_plus"
#-----------后台等待 activeBaiduid-------------------
oneday_activeBaiduid_plus(){
    hadoop_dir_search $jobname7 1 "activeBaiduid" # 检查并查找可替代的baiduid-uid数据---程序产出
    check_jobs_input $jobname7 1  "oneday_baiduid_plus" 0 $MOBILE_LIST_STRA # 内部依赖
    oneday_activeBaiduid_plus_input="${activeBaiduid_dir} ${oneday_baiduid_plus_output}"
    start_hadoop_jobs $jobname7  $oneday_activeBaiduid_plus_input_str1 $oneday_activeBaiduid_plus_input_str2 
    reset_activeBaiduid # 恢复activeBaiduid原始值
}
 
if [ $oneday_activeBaiduid_plus_pass -eq 0 ];then
    oneday_activeBaiduid_plus
fi
 
#------------oneday_dump_urlUnify----------------------
#  作用: 用网页库查询结果归一化全库数据database_dump   |
#  输入: databse_dump和oneday_onlyUrl_mini             |
#  输出: oneday_dump_urlUnify                          |
#-------------------------------------------------------
jobname8="oneday_dump_urlUnify"
oneday_dump_urlUnify(){ # 后台运行
    check_jobs_input $jobname8 2  "database_dump" 0 $MOBILE_LIST_ALL # 外部依赖
    check_jobs_input $jobname8 1  "oneday_onlyUrl_mini_plus" 0 $MOBILE_LIST_STRA # 内部依赖
    start_hadoop_jobs $jobname8 $oneday_dump_urlUnify_input_str1 $oneday_dump_urlUnify_input_str2
}
 
if [ $oneday_dump_urlUnify_pass -eq 0 ];then
    oneday_dump_urlUnify  &
fi
 
#------------------end-----------------------
log "[`date "+%Y-%m-%d %H:%M:%S"`] [NOTE] oneday-pretreat success ! oneday-pretreat 模块执行完毕!"
 
#关闭重定向
exec 3<&-

批主机管理

pdsh、pssh 是之前运维管理人员的利器。

这俩工具已退出历史舞台,现在发光发热的已是 Ansible、Salt。

pssh

pssh 是一个 python 编写可以在多台服务器上执行命令的工具,同时支持拷贝文件,是同类工具中很出色的,类似 pdsh 。为方便操作,使用前请在各个服务器上配置好密钥认证访问。

附加工具

  • pscp 传输文件到多个 hosts,类似 scp
    • pscp -h hosts.txt -l irb2 foo.txt /home/irb2/foo.txt
  • pslurp 从多台远程机器拷贝文件到本地
  • pnuke 并行在远程主机杀进程
    • pnuke -h hosts.txt -l irb2 java
  • prsync 使用rsync协议从本地计算机同步到远程主机
    • prsync -r -h hosts.txt -l irb2 foo /home/irb2/foo

pdsh

pdsh (Parallel Distributed Shell) 可并行执行对目标主机的操作,对于批量执行命令和分发任务有很大的帮助,在使用前需要配置 ssh 无密码登录

pdsh 全称 parallel distributed shell

  • 与pssh类似,pdsh可并行执行对远程目标主机的操作,在有批量执行命令或分发任务的运维需求时,使用这个命令可达到事半功倍的效果。
  • 同时,pdsh还支持交互模式,当要执行的命令不确定时,可直接进入pdsh命令行,非常方便。

pdsh应用场景

  • 基本上与pssh相同,都用于大批量服务器的配置、部署、文件复制等运维操作。
  • 使用pdsh时,仍需要配置本地主机和远程主机间的单向ssh信任。
  • 另外,pdsh还附带了pdcp命令,此命令可以将本地文件批量复制到远程的多台主机上,这在大规模的文件分发环境下是非常有用的。

pdsh可以通过多种方式在远程主机上运行命令

  • 默认是rsh方式
  • 另外也支持ssh、mrsh、qsh、mqsh、krb4、xcpu等多种rcmd模块,这个可以在运行命令时通过参数指定。
pdsh -h
Usage: pdsh [-options] command ...
-S                return largest of remote command return values
-h                output usage menu and quit                获取帮助
-V                output version information and quit       查看版本
-q                list the option settings and quit         列出 `pdsh` 执行的一些信息
-b                disable ^C status feature (batch mode)
-d                enable extra debug information from ^C status
-l user           execute remote commands as user           指定远程使用的用户
-t seconds        set connect timeout (default is 10 sec)   指定超时时间
-u seconds        set command timeout (no default)          类似 `-t`
-f n              use fanout of n nodes                     设置同时连接的目标主机的个数
-w host,host,...  set target node list on command line      指定主机,host 可以是主机名也可以是 ip
-x host,host,...  set node exclusion list on command line   排除某些或者某个主机
-R name           set rcmd module to name                   指定 rcmd 的模块名,默认使用 ssh
-N                disable hostname: labels on output lines  输出不显示主机名或者 ip
-L                list info on all loaded modules and exit  列出 `pdsh` 加载的模块信息
-a                target all nodes                          指定所有的节点
-g groupname      target hosts in dsh group "groupname"     指定 `dsh` 组名,编译安裝需要添加 `-g` 支持选项 `--with-dshgroups`
-X groupname      exclude hosts in dsh group "groupname"    排除组,一般和 `-a` 连用
available rcmd modules: exec,xcpu,ssh (default: ssh)        可用的执行命令模块,默认为 ssh

示例

# 单个主机测试
pdsh -w 192.168.0.231 -l root uptime
# 192.168.0.231:  16:16:11 up 32 days, 22:14, ? users,  load average: 0.10, 0.14, 0.16
# 多个主机测试
pdsh -w 192.168.0.[231-233] -l root uptime
# 192.168.0.233:  16:17:05 up 32 days, 22:17, ? users,  load average: 0.13, 0.12, 0.10
# 192.168.0.232:  16:17:05 up 32 days, 22:17, ? users,  load average: 0.45, 0.34, 0.27
# 192.168.0.231:  16:17:06 up 32 days, 22:15, ? users,  load average: 0.09, 0.13, 0.15
# 逗号分隔主机
pdsh -w 192.168.0.231,192.168.0.234 -l root uptime
# 192.168.0.234:  16:19:44 up 32 days, 22:19, ? users,  load average: 0.17, 0.21, 0.20
# 192.168.0.231:  16:19:44 up 32 days, 22:17, ? users,  load average: 0.29, 0.18, 0.16
#  -x 排除某个主机
pdsh -w 192.168.0.[231-233] -x 192.168.0.232 -l root uptime
# 192.168.0.233:  16:18:24 up 32 days, 22:19, ? users,  load average: 0.11, 0.12, 0.09
# 192.168.0.231:  16:18:25 up 32 days, 22:16, ? users,  load average: 0.11, 0.13, 0.15

【2024-4-20】pdsh 安装

sudo apt-get install pdsh
# 错误
pdsh -v
# pdsh@mlxlabqcvytu4a66056efe-20240328132206-317dg9-nqi3xt-worker: module path "/usr/lib/x86_64-linux-gnu/pdsh" insecure.
# pdsh@mlxlabqcvytu4a66056efe-20240328132206-317dg9-nqi3xt-worker: "/": Owner not root, current uid, or pdsh executable owner
# pdsh@mlxlabqcvytu4a66056efe-20240328132206-317dg9-nqi3xt-worker: Couldn't load any pdsh modules

# 源码编译安装
wget https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/pdsh/pdsh-2.29.tar.bz2
./configure --with-ssh --enable-static-modules --prefix=/home/wqw/bin && make && make install
# 问题: error: invalid variable name: –-prefix
# 解决: 复制过来的 -- 格式有问题, 重新输入
# 编译完后,生成 bin文件夹 /home/wqw/bin/pdsh/bin
# 修改配置 vim ~/.bashrc
export PATH=$PATH:/home/wqw/pdsh/bin

source ~/.bashrc

pdsh-2.29 (+static-modules)
# rcmd modules: ssh,rsh,exec (default: rsh)
# misc modules: (none)

结束


支付宝打赏 微信打赏

~ 海内存知已,天涯若比邻 ~

Share

Related Posts

上一篇 宗教-Religion

下一篇 Linux 技能总结

标题:宗教-Religion

摘要:宗教是什么,为什么这么多人信,龙泉寺第三届IT禅修营见闻

标题:Linux 技能总结

摘要:Linux 使用技能总结,持续更新

站内可视化导航

文章可视化导读:鼠标划过图形块时,如果出现蓝色光环, 点击即可跳转到对应主题

Comments

--disqus--

    Content
    My Moment ( 微信公众号 )
    欢迎关注鹤啸九天