Shell语言
更多编程语言介绍:
- 编程语言发展历史
- 【2022-11-20】linux bash shell 最常用的函数和指令
- 【2024-3-5】Bash 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=1getopt
: 外部工具,支持长选项
getopt
与 getopts
都是 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
局限:
- 选项参数的格式必须是
-d val
,中间没有空格的-dval
, 不行。 - 所有选项参数必须写在其它参数的前面,因为getopts是从命令行前面开始处理,遇到非
-
开头的参数,或者选项参数结束标记--
就中止了,如果中间遇到非选项的命令行参数,后面的选项参数就都取不到了。 - 不支持
长选项
, 也就是--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
,修改默认shsudo 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
时间日期
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
彩色日志
- 特效格式
- \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。
- 【2024-4-19】参考
pssh
pssh 是一个 python 编写可以在多台服务器上执行命令的工具,同时支持拷贝文件,是同类工具中很出色的,类似 pdsh 。为方便操作,使用前请在各个服务器上配置好密钥认证访问。
- 项目地址: parallel-ssh
- 总结
附加工具
- 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 – 错误
- 源码安装
- google code
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)