shell总结


菜单

 

生成菜单1:

#!/bin/bash # 定义颜色变量 RED='\033[1;31m' GREEN='\033[32m' YELLOW='\033[33m' BLUE='\033[34m' NORMAL='\033[0m' PS3=`echo -e "${GREEN}请选择一个选项:${NORMAL}" ` options=("选项1" "选项2" "选项3" "退出") select opt in "${options[@]}" do case "$opt" in "选项1") echo -e "${YELLOW}你选择了选项1${NORMAL}" ;; "选项2") echo -e "${YELLOW}你选择了选项2${NORMAL}" ;; "选项3") echo -e "${YELLOW}你选择了选项3${NORMAL}" ;; "退出") echo -e "${RED}退出程序${NORMAL}" break ;; *) echo -e "${RED}无效选项${NORMAL}";; esac done
生成菜单法2:

cat <<-EOF #cat h 显示命令帮助 f 显示磁盘分区 d 显示磁盘挂载 m 查看内存使用 u 查看系统负载 q 退出程序 bag show rosbag EOF while true #死循环 do #选择操作的内容 read -p "选择内容(help h):" action #clear case $action in #case语句 h) cat <<-EOF #conmod h 显示命令帮助 f 显示磁盘分区 d 显示磁盘挂载 m 查看内存使用 u 查看系统负载 q 退出程序 bag show rosbag EOF ;; f) fdisk -l ;; d) df -h ;; m) free -m ;; u) uptime ;; q) exit ;; bag) ssh -p 17042 cti@frp.ctirobot.com "ls -lh .ros/cti_all_bag" ;; esac done

 

#!/usr/bin/env #创建用户 USER=$1 ROOT_PASSWD="Fpc11100885" INIT_PASSWD="fPc11100885" #免密 echo "" |ssh-keygen -t rsa -P "" while read host do sshpass -p "$ROOT_PASSWD" ssh -n -o StrictHostKeyChecking=no -l root $host "userdel -r $USER;useradd $USER;echo "$INIT_PASSWD"|passwd --stdin $USER" sshpass -p "$INIT_PASSWD" ssh-copy-id $USER@$host done<$2

 

日志输出: 

要想将脚本中所有的标准输出1和标准错误输出2都自动写到log中,可以在脚本开头加上

exec &>>./a.log 或者exec 1>>a.log 2>&1

 


shell组命令与子进程

  • 1.组命令
  • 2.子进程
    • 2.1 什么是子进程
    • 2.2 创建子进程
    • 2.3 子进程总结
  • 3.如何检测子shell与子进程
  • 4.当前shell运行

1.组命令

组命令,就是将多个命令划分为一组,或者看成一个整体。

用法
区别

Shell 组命令的写法有两种:

{ command1; command2;. . .;  }
(command1; command2;. . . )

  1. 由花括号{}包围起来的组命名在当前 Shell 进程中执行,而由小括号()包围起来的组命令会创建一个子 Shell,所有命令都在子 Shell 中执行。
  2. 使用花括号{}时,花括号与命令之间必须要有一个空格,并且最后一个命令必须用一个分号或一个换行符结束
  3. 组命令可以将多条命令的输出结果合并在一起,在使用重定向和管道时会特别方便。

两种写法的重要不同:{}包围的组命令在当前 Shell 进程中执行,由()包围的组命令会创建一个子Shell,所有命令都会在这个子 Shell 中执行
在子 Shell 中执行意味着,运行环境被复制给了一个新的 shell 进程,当这个子 Shell 退出时,新的进程也会被销毁,环境副本也会消失,

所以在子 Shell 环境中的任何更改都会消失(包括给变量赋值)。因此,在大多数情况下,除非脚本要求一个子 Shell,

否则使用{}比使用()更受欢迎,并且{}的进行速度更快,占用的内存更少

举栗 
将多条命令的输出重定向到out.txt文件

1.普通模式

  1. ls -l > out.txt #>表示覆盖
  2. echo "test432" >> out.txt #>>表示追加
  3. cat test.txt >> out.txt

2.使用组命令

{ ls -l ;echo "test432";cat test.txt; }>out.txt

(ls -l ;echo "test432";cat test.txt)>out.txt

组命令与管道结合

(ls -l ;echo "test432";cat ../test.txt)|wc -l

{ { } } 和( ( ))组命令区别


echo "test" | { { { echo "$SHLVL $BASH_SUBSHELL"; } } }  
echo "$SHLVL" "$BASH_SUBSHELL"

4 1
4 0

多个{}嵌套,{和{要有空格,且多层嵌套也只产生1层子shell


( ( ( echo $SHLVL $BASH_SUBSHELL) ) )
echo $SHLVL $BASH_SUBSHELL

4 3
4 0

多个()嵌套,(和(要有空格,且多层嵌套对产生多层子shell

2.子进程

2.1 什么是子进程

 

子进程的概念是由父进程的概念引申而来的。在 Linux 系统中,系统运行的应用程序几乎都是从 init(pid为 1 的进程)进程派生而来的,所有这些应用程序都可以视为 init 进程的子进程,而 init 则为它们的父进程。

 

Shell 脚本是从上至下、从左至右依次执行的,即执行完一个命令之后再执行下一个。如果在 Shell 脚本中遇到子脚本(即脚本嵌套,但是必须以新进程的方式运行)或者外部命令,就会向系统内核申请创建一个新的进程,以便在该进程中执行子脚本或者外部命令,这个新的进程就是子进程。子进程执行完毕后才能回到父进程,才能继续执行父脚本中后续的命令及语句。

 

使用pstree -p命令就可以看到 init 及系统中其他进程的进程树信息(包括 pid):

 

systemd(1)─┬─ModemManager(796)─┬─{ModemManager}(821) │ └─{ModemManager}(882) ├─NetworkManager(975)─┬─{NetworkManager}(1061) │ └─{NetworkManager}(1077) ├─abrt-watch-log(774) ├─abrt-watch-log(776) ├─abrtd(773) ├─accounts-daemon(806)─┬─{accounts-daemon}(839) │ └─{accounts-daemon}(883) ├─alsactl(768) ├─at-spi-bus-laun(1954)─┬─dbus-daemon(1958)───{dbus-daemon}(1960) │ ├─{at-spi-bus-laun}(1955) │ ├─{at-spi-bus-laun}(1957) │ └─{at-spi-bus-laun}(1959) ├─at-spi2-registr(1962)───{at-spi2-registr}(1965) ├─atd(842) ├─auditd(739)─┬─audispd(753)─┬─sedispatch(757) │ │ └─{audispd}(759) │ └─{auditd}(752)

 

2.2 创建子进程

创建子进程的方式
说明
 
  • 第一种只使用 fork() 函数,子进程和父进程几乎是一模一样的,父进程中的函数、变量(全局变量、局部变量)、文件描述符、别名等在子进程中仍然有效。我们将这种子进程称为子 Shell(sub shell)

 

  1. 使用 fork() 函数创建一个子进程相当于对父进程进行了克隆,除了 PID(进程ID)等极少的参数不同外,子进程的一切都来自父进程,包括代码、数据、堆栈、打开的文件等,就连代码的执行位置(状态)都是一样的。
  2. 但是,后期随着各自的发展轨迹不同,两者会变得不一样,但是在 fork() 出来的那一刻,两者都是一样的。
组命令、命令替换、管道
  • 第二种使用 fork() 和 exec() 函数,即使用 fork()创建子进程后立即调用 exec() 函数加载新的可执行文件,而不使用从父进程继承来的一切,子进程和父进程之间除了硬生生地维持一种“父子关系”外,再也没有任何联系了,它们就是两个完全不同的程序。

举栗:
在 ~/bin 目录下有两个可执行文件分别叫 a.out 和 b.out。现在运行 a.out,就会产生一个进程,比如叫做 A。在进程 A 中我又调用 fork() 函数创建了一个进程 B,那么 B 就是 A 的子进程,此时它们是一模一样的。但是,我调用 fork() 后立即又调用 exec() 去加载 b.out,这可就坏事了,B 进程中的一切(包括代码、数据、堆栈等)都会被销毁,然后再根据 b.out 重建建立一切。这样一折腾,B 进程除了 ID 没有变,其它的都变了,再也没有属于 A 的东西了。

1.以新进程的方式运行脚本文件,比如bash ./test.sh; chmod +x ./test.sh; ./test.sh

2.在当前 Shell 中使用 bash 命令启动新的 Shell

2.3 子进程总结

子 Shell 虽然能使用父 Shell 的的一切,但是如果子 Shell 对数据做了修改,比如修改了全局变量,这种修改也只能停留在子 Shell,无法传递给父 Shell。不管是子进程还是子 Shell,都是“传子不传父”。

子 Shell 才是真正继承了父进程的一切,这才像“一个模子刻出来的”;普通子进程和父进程是完全不同的两个程序,只是维持着父子关系而已。

3.如何检测子shell与子进程

echo $$输出当前进程ID (当前shell的pid,也就是脚本运行的当前进程号),echo $PPID输出父shell ID

 

命令

结果

结论

输出当前进程与父进程ID

echo $$;echo $PPID

34451

34450

 

子进程形式输出进程ID

子进程

bash

echo $$;echo $PPID

exit

52886

34451

在普通的子进程中,$ 被展开为子进程的 ID

组命令形式输出进程ID

子shell

(echo $$;echo $PPID)

 

34451

34450

子shell和父shell中的ID是一样的

这是因为$ 变量在子 Shell 中无效!Base 官方文档说,在普通的子进程中,$ 确实被展开为子进程的 ID;

但是在子 Shell 中,$ 却被展开成父进程的 ID

管道形式输出进程ID

子shell

echo "test" | { echo $$;echo $PPID; }

34451

34450

进程替换形式输出进程ID

read < <(echo $$ $PPID)

$ echo $REPLY

34451 34450


除了 $,Bash 还提供了另外两个环境变量——SHLVL 和 BASH_SUBSHELL,用它们来检测子 Shell 非常方便。

SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,每次进入一层普通的子进程,SHLVL 的值就加 1。而 BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子 Shell(sub shell)嵌套深度的累加器,每次进入一层子 Shell,BASH_SUBSHELL 的值就加 1。

 
 
 命令结果知识点
输出变量

echo "$SHLVL $BASH_SUBSHELL"

1  0

 

子进程形式输出变量

子进程

创建一个脚本文件,命名为 test.sh,内容如下:

#!/bin/bash echo "$SHLVL $BASH_SUBSHELL"

*****************************************

bash

echo "$SHLVL $BASH_SUBSHELL"#2 0

bash ./test.sh #3 0

echo "$SHLVL $BASH_SUBSHELL"#2 0

chmod +x ./test.sh;./test.sh #3 0

echo "$SHLVL $BASH_SUBSHELL"#2 0

exit #退出内层Shell

echo "$SHLVL $BASH_SUBSHELL"#1 0

 

bash ./test.sh./test.sh

这两种运行脚本的方式,在脚本运行期间会开启一个子进程,

运行结束后立即退出子进程

产生新进程时,SHLVL的值加1

组命令形式输出变量

子shell

(echo "$SHLVL  $BASH_SUBSHELL")

1  1 组命令、管道、命令替换这几种方式都会产生子 Shell

管道形式输出变量

子shell

echo "test" | { echo "$SHLVL  $BASH_SUBSHELL"; }

1  1

命令替换形式输出变量

子shell

var=$(echo "$SHLVL  $BASH_SUBSHELL")

echo $var

1 1

四层组命令形式输出变量

子shell

( ( ( (echo "$SHLVL $BASH_SUBSHELL") ) ) )

1 4

进程替换形式输出变量

 

read < <(echo "$SHLVL  $BASH_SUBSHELL")

echo $REPLY

 

echo "hello" > >(echo "$SHLVL $BASH_SUBSHELL")

 

1 0

1 0

进程替换只是借助文件在()内部和外部命令之间传递数据,

并没有创建子shell,()内部和外部的命令是在一个进程

(也就是当前进程)中执行的

 

 

4 当前shell运行,是内置命令

调用所有普通的程序(包括另一个shell程序、其他sh脚本)都是子进程。
只有在命令中写明(),$(),``(反引号) 都是用来执行命令替换的,它们会在子 shell 中执行。比如echo $(ls),这里先用$()产生一个子shell,在调用ls时,又在这个子shell内部产生了一个专门执行ls的子进程,这里存在嵌套

内建命令就是包含在bash Shell工具包中的命令,是bash Shell的骨干部分,除此之外,保留字(reserved words)也是bash Shell的骨干部分,保留字对于bash Shell具有特殊的含义,用来构建Shell语法结构,forifthenwhileuntil等都是保留字,但是,保留字本身不是一个命令,而是命令结构的一部分。

Bash Shell的内建命令和保留字:

$(( )) $[] expr等  行算术运算时,不需要启动新的 shell 进程,所有的计算都是在当前的 shell 环境

                #保留字,逻辑非

:                 #不做任何事,只做参数展开

.                 #读取文件,并在当前Shell中执行它

alias           #设置命令或命令行的别名

bg              #将作业置于后台运行

bind           #将关键字序列与readline函数或宏绑定

break         #保留字,跳出forwhileuntilselect循环

builtin        #调用命令的内建命令格式,而禁用同名的函数,或者同名的扩展命令

case           #保留字,多重选择

cd              #切换当前工作目录

command  #找出内建和外部命令;寻找内建命令而非同名函数

continue    #保留字,到达下一次forwhileuntilselect循环

declare      #声明变量,定义变量属性

dirs            #显示当前存储目录的列表

disown       #将作业从表中移除

do              #保留字,forwhileuntilselect循环的一部分

done          #保留字,forwhileuntilselect循环的一部分

echo          #打印参数

elif             #保留字,if结构的一部分

else           #保留字,if结构的一部分

enable       #开启和关闭内建命令

esac          #保留字,case的一部分

eval           #将参数作为命令再处理一遍

exec          #以特定程序取代Shell或为Shell改变I/O

exit           #退出Shell

export       #将变量声明为环境变量

fc              #与命令历史一起运行

fg              #将作业置于前台运行

fi               #保留字,if结构的一部分

for             #保留字,for结构的一部分

function    #定义一个函数

getops      #处理命令行选项

hash         #记录并指定命令的路径名

help          #显示内建命令的帮助信息

history      #显示历史命令信息

if               #保留字,if结构的一部分

in              #保留字,case的一部分

jobs          #显示在后台运行的作业

kill            #向进程传送信号

let             #使变量执行算术运算

local          #定义局部变量

logout       #shell中注销

popd         #从目录栈中弹出目录

pushd        #将目录压入目录栈

pwd           #显示当前工作目录

read          #从标准输入中读入一行

readonly   #将变量定义为只读

return       #从函数或脚本中返回

select       #保留字,生成选择菜单

set            #设置Shell选项

shift          #变换命令行参数

suspend   #中止Shell的执行

test           #评估条件表达式

then          #保留字,if结构的一部分

time #保留字,输出统计出来的命令执行时间,其输出格式由TIMEFORMAT变量来控制

times        #针对Shell及其子Shell,显示用户和系统CPU的时间之和

trap          #设置信号捕捉程序

type          #确认命令的源

typeset     #声明变量,定义变量属性,与declare等价

ulimit        #设置和显示进程占用资源的限制

umask      #设置和显示文件权限码

unalias      #取消别名定义

unset        #取消变量或函数的定义

until          #保留字,一种循环结构

wait          #等待后台作业完成

while         #保留字,一种循环结构

 

冒号是bash Shell中一个特殊的符号,可表示永真,相当于TRUE关键字。

例:冒号表示永真的用法

#!/bin/bash

i=0

while :        #冒号相当于TRUE

do

        if ((i >= 3 ))

        then

                break

        fi

        echo $((++i))

done

结果:1

          2

          3

 

冒号可以清空一个文件。:>命令是常用的清空文件的命令

命令:cat loggg

结果:Positional Parameter is NULL

命令::>loggg          #将冒号重定向到文件并将文件清空

          cat loggg

结果:

 

冒号最重要的用法是:不做任何事,只做参数展开。

 

12.1.2、圆括号结构
圆括号结构能够强制将其中的命令运行在子Shell中,其基本格式为:
(
command1
command2
...
commandn
)
该结构表示圆括号内的n条命令在子Shell中运行,bash版本3之后定义的内部变量BASH_SUBSHELL记录了子Shell的层次。

 

例:圆括号结构用法和BASH_SUBSHELL变量
#!/bin/bash
echo "The level of father Shell is: $BASH_SUBSHELL" #打印父Shell的层次
outervar=OUTER #定义一个变量
( #进入子Shell
echo "The level of SubShell is: $BASH_SUBSHELL"
innervar=INNER #在子Shell内定义一个变量
echo "innervar=$innervar"
echo "outervar=$outervar"
)
#回到父Shell
echo "The level of father Shell is: $BASH_SUBSHELL"
if [ -z "$innervar" ] #测试子Shell中定义的变量是否为空
then
echo "The \$innervar is not defined in main body."
else
echo "The \$innervar is defined in main body."
fi
结果:The level of father Shell is: 0 #父Shell的BASH_SUBSHELL值
The level of SubShell is: 1 #子Shell的BASH_SUBSHELL值
innervar=INNER
outervar=OUTER #子Shell能使用父Shell定义的变量
The level of father Shell is: 0
#子Shell定义的变量innervar在父Shell中为空,说明子Shell中变量对父Shell是不可见的
The $innervar is not defined in main body.

 

BASH_SUBSHELL是从0开始计数的整数,它依次记录子Shell的层次。

 

例:测试子Shell定义环境变量是否对父Shell有效
#!/bin/bash
#在父Shell中定义变量outervar
echo "-----------------IN MAINSHELL--------------------"
outervar=OUTER
echo "outervar=$outervar"
( #进入子Shell
echo "-----------------IN SUBSHELL---------------------"
innervar=INNER
echo "innervar=$innervar"
outervar=OUTER-INNER #更改父Shell所定义的outervar变量值
echo "outervar=$outervar"
#将innervar和outervar声明为环境变量
export innervar
export outervar
)
#回到父Shell,测试innervar和outervar的值是否与子Shell中的定义一样
echo "-----------------RETURN TO MAINSHELL-------------"
echo "innerver=$innervar"
echo "outervar=$outervar"
结果:-----------------IN MAINSHELL--------------------
outervar=OUTER
-----------------IN SUBSHELL---------------------
#下面打印子Shell中innervar和outervar变量的值
innervar=INNER
outervar=OUTER-INNER
-----------------RETURN TO MAINSHELL-------------
innerver=
outervar=OUTER
#父shell中innervar为空,outervar仍为原来的值,这说明子Shell对两个变量
的定义和更改对父Shell不起作用

 

子Shell只能继承父Shell的一些属性,但是,子Shell不可能反过来改变父Shell的属性。
子Shell能够从父Shell继承得来的属性有:
当前工作目录
环境变量
标准输入、标准输出和标准错误输出
所有已打开的文件标识符
忽略的信号
子Shell不能从父Shell继承得来的属性有:
除了环境变量和 .bashrc文件中定义变量之外的Shell变量
未被忽略的信号处理
因此,子Shell能够设置独立于父Shell的子环境。

 

例:子Shell设定bash Shell选项的用法
#!/bin/bash
(
set -C #开启-C选项防止重定向时覆盖文件
:> outputnull #试图用冒号清空outputnull文件
)
#在父Shell覆盖一个文件,测试子Shell开启的-C选项是否对父Shell生效
cat subsenv.sh > outputnull
执行:./subsenv.sh
结果:./subsenv.sh:行4: outputnull: 无法覆盖已存在的文件
#提示执行脚本时出错,不能覆盖已存在的文件,这说明set -C在子Shell中已经起作用
执行:cat outputnull
#查看outputnull内容,为subsenv.sh脚本本身,这说明父Shell仍能覆盖文件,set -C不起作用
结果:#!/bin/bash
(
set -C
:> outputnull
)
cat subsenv.sh > outputnull

 

例:利用子Shell测试变量是否定义过
#!/bin/bash
if (set -u; : $var)
then
echo "Variable is set."
fi
执行:var=2012
export var #将var声明为环境变量
结果:Variable is set. #该脚本能测试出var已经定义
set -u命令用于设置Shell选项,u选项是nounset的意思,表示当使用未定义的变量时,输出错误信息,并强制退出。

 

子Shell还可以接收到父Shell从管道传送来的数据。

 

例:打印/etc/passwd文件中与root关键字所匹配的行
执行:cat /etc/passwd | (grep 'root')
结果:root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

 

子Shell可以将一个计算量较大的任务分成若干个小任务并行执行。

 

例:子Shell用于并行计算的用法
#!/bin/bash
#用圆括号结构创建三个子Shell同时执行,每个子Shell都是搜索某个目录下与root关键字匹配的行,排序后输出到某文件
(grep -r "root" /etc/* | sort > part1) &
(grep -r "root" /usr/local/* | sort > part2) &
(grep -r "root" /lib/* | sort > part3) &
wait #等待后台执行的作业全部完成后,再执行下面的命令
#将part1、part2和part3三个临时文件合并,排序后重定向到parttotal文件
cat part1 part2 part3 | sort > parttotal
echo "Run time of this script is: $SECONDS" #输出该脚本的执行时间
结果:Run time of this script is: 22 #脚本运行了22秒
执行:wc -l parttotal #对结果文件的行计数
结果:1492 parttotal

 

grep的-r选项表示递归搜索。每个圆括号结构之外都有一个&符号,这表示将此命令放在后台执行,继续执行下一条命令,如果去掉&符号,脚本需要将(grep -r "root" /etc/* | sort > part1)命令执行完毕后才能执行下一条命令。wait
命令是一个内建命令,用于等待后台执行的作业全部完成后再执行下面的命令。假如没有这条命令,脚本将三个子Shell放到后台执行后,将直接执行合并临时文件的命令,此时,三个子Shell可能并未执行完毕。因此,临时文件中的结果是不完整的,合并后也将产生不完整的最终结果。

 

子Shell是允许嵌套调用的,可在函数或圆括号结构内再次调用圆括号结构创建子Shell。

 

12.2、Shell的限制模式
Shell的限制模式简称RSH(Restricted Shell),处于限制模式的Shell下运行一个脚本或脚本片段,将会禁用一些命令或操作。Shell的限制模式是Linux系统基于安全方面的考虑,目的是为了限制脚本用户的权限,并尽可能地减小脚本所带来的危害。
Shell的限制模式限制的命令和操作有:
用cd命令更改当前工作目录的命令。
更改重要环境变量的值,包括$PATH、$SHELL、$BASH_ENV、$ENV和$SHELLOPTS
输出重定向符号,包括>、>>、>|、>&、<>和&>符号。
调用含有一个或多个斜杠(/)的命令名称。
使用内建命令exec。
使用set +r等命令关闭限制模式。

 

例:Shell的正常模式和限制模式的区别
#!/bin/bash
#在正常模式下改变当前工作目录
echo "Changing current work directory"
cd /etc
echo "Now in $PWD"
set -r #利用Shell选项使下面的代码运行在限制模式下,r是restricted的简写
echo
echo "------------IN RESTRICTED MODE-------------"
#验证在限制模式下能否改变当前工作目录
echo "Trying to change current work directory"
cd /usr/local
echo "Now in `pwd`"
echo
#验证在限制模式下能否改变$SHELL变量的值
echo "Trying to change \$SHELL"
SHELL="/bin/sh"
echo "\$SHELL=$SHELL"
echo
#验证在限制模式下能否执行重定向操作
echo "Trying to redirect output to a file"
who > outputnull
ls -l outputnull
结果:Changing current work directory
Now in /etc #Shell在正常模式下改变当前工作目录

 

------------IN RESTRICTED MODE-------------
#开始在限制模式下运行
Trying to change current work directory
./resshell.sh: 第 9 行:cd: 受限的 #cd命令出错,被限制了
Now in /etc #当前工作目录没有改变

 

Trying to change $SHELL
./resshell.sh:行13: SHELL: 只读变量 #SHELL变量在限制模式下是只读的
$SHELL=/bin/bash

 

Trying to redirect output to a file
./resshell.sh:行17: outputnull: 受限的: 无法重定向输出 #redirect操作出错

 

ls: 无法访问outputnull: 没有那个文件或目录 #outputnull没有被创建

 

还有一种以限制模式运行脚本的方式,就是将Sha-bang符号(#!)后的语句改成/bin/bash -r,-r表示在限制模式下运行该脚本。

 

例:以/bin/bash -r方式进入限制模式
#!/bin/bash -r
#验证在限制模式下能否读取$SHELLOPTS变量的值
echo "\$SHELLOPTS=$SHELLOPTS"
echo
echo "Changing current work directory"
cd /etc
echo "Now in $PWD"
echo
echo "Trying to change \$SHELL"
SHELL="/bin/sh"
echo "\$SHELL=$SHELL"
echo
echo "Trying to redirect output to a file"
who > outputnull
ls -l outputnull
结果:$SHELLOPTS=braceexpand:hashall:interactive-comments
#能够读取$SHELLOPTS的值
Changing current work directory
./anotherres.sh: 第 5 行:cd: 受限的
Now in /home/xiaomiao/test

 

Trying to change $SHELL
./anotherres.sh:行9: SHELL: 只读变量
$SHELL=/bin/bash

 

Trying to redirect output to a file
./anotherres.sh:行13: outputnull: 受限的: 无法重定向输出
-rw-rw-r-- 1 xiaomiao xiaomiao 65 8月 14 11:49 outputnull

 

12.3、进程处理
fork是Linux系统的一种系统调用(system calls),系统调用用于请求内核服务,这也是进程访问硬件的唯一方法。fork是创建新进程的系统调用,fork创建的子进程是父进程的副本,两个进程具有同样的环境、打开的文件、用户标志符、当前工作目录和信号等。
UNIX是第一个允许每个系统用户控制多个进程的操作系统,这种机制称为用户控制的多任务(user-controlled multitasking),Linux操作系统延续了此特性。

 

12.3.1、进程和作业
进程和作业是有区别的:一个正在执行的进程称为作业,一个作业可以包含多个进程。因此,作业是用户层面的概念,而进程是操作系统层面的概念。
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,进程在运行中不断地改变其运行状态。
通常,一个运行进程必须具有以下三种基本状态:就绪(Ready)状态、运行(Running)状态和阻塞(Blocked)状态。

 

引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信号等。
一个进程在运行期间,不断地从一种状态转换到另一种状态,它可以多次处于就绪状态和执行状态,也可以多次处于阻塞状态。
Linux系统为每个进程分配一个数字以标识这个进程,这个数字就是进程号。同时,创建该进程的Shell为此进程创建一个数字,也用于标识这个进程,这个数字称为作业号。
作业号属于Shell,进程号属于Linux。
作业号标识的是在此Shell下运行的所有进程。

 

后台运行一个作业,方括号中的[1]是作业号,方括号后面的6355是进程号
执行:grep -r "root" /etc/* | sort > part1 &
结果:[1] 6355
执行:grep -r "root" /usr/local/* | sort > part2 &
结果:[2] 6409
[1] 完成 grep --color=auto -r "root" /etc/* | sort > part1
#提示[1]号作业已经完成

 

默认情况下,当我们输入下一条命令时,Shell才提示后台运行的作业已经结束,而实际上,该作业可能已经早就运行结束。如果需要当后台运行的作业一结束,Shell就显示信息,就需要开启norify选项,简写为b。

 

norify选项的功能
执行:set -b #开启norify选项
grep -r "root" /etc/* | sort > part1 &
结果:[1] 6545
结果:[1]+ 完成 grep --color=auto -r "root" /etc/* | sort > part1

 

12.3.2、作业控制
进程是针对整个Linux系统而言的,作业是针对Shell而言的。
作业有两种运行方式:前台运行和后台运行。前台运行的作业指作业能够控制当前终端或窗口,且能接收用户的输入;而后台运行的作业则不在当前激活的终端或窗口中运行,是在用户“看不见”的情况下运行。内建命令fg可将后台运行的作业放到前台,而&符号使得作业在后台运行。

 

Sleep10.sh脚本,休眠10秒后结束
#!/bin/bash
sleep 10
执行:./sleep10.sh &
结果:[1] 6707
执行:fg #将[1]号作业放到前台运行
结果:./sleep10.sh
#Shell等待[1]号作业运行完毕,才显示下一行提示符

 

只有一个作业在后台运行,fg命令不带任何参数就能将该作业放到前台运行。当有多个作业在后台运行时,不带任何参数的fg命令将最近提交的那个后台作业放置到前台。fg命令如果要在多个后台作业中挑选符合条件的作业,可使用作业号、作业的命令字符等参数。

 

fg命令利用作业号指定作业
执行:./sleep20.sh & #提交第1个后台作业
结果:[1] 6819
执行:./sleep10.sh & #提交第2个后台作业
结果:[2] 6821
执行:fg %1 #将[1]号作业放到前台运行
结果:./sleep20.sh #[1]号作业是sleep20.sh脚本
[2] 完成 ./sleep10.sh
#在等待[1]号作业的过程中,[2]号作业运行完毕

 

指定作业方法及其意义:
%n #n为后台作业的作业号
%string #命令以string字符串开始的后台作业
%?string #命令包含string字符串的后台作业
%+或%% #最近提交的后台作业
%- #最近第二个提交的后台作业

 

jobs命令用于显示所有的后台作业。

 

jobs命令的用法:
执行:./sleep20.sh &
结果:[1] 6992
执行:./sleep20.sh &
结果:[2] 6994
执行:./sleep10.sh &
结果:[3] 6996
执行:jobs #显示所有后台运行的作业
结果:[1] 运行中 ./sleep20.sh &
[2]- 运行中 ./sleep20.sh &
[3]+ 运行中 ./sleep10.sh &
执行:jobs -l #带上-l参数,显示作业的进程号
结果:[1] 6992 运行中 ./sleep20.sh &
[2]- 6994 运行中 ./sleep20.sh &
[3]+ 6996 运行中 ./sleep10.sh &

 

在作业运行时按下“Ctrl+Z”组合键即可将正在运行的作业阻塞。

 

将正在运行的作业阻塞的用法:
执行:vim sleep10.sh #用vim打开文件
#!/bin/bash
sleep 10

#按下“Ctrl+Z”组合键,出现如下信息,说明vim sleep10.sh作业进入阻塞状态
[1]+ 已停止 vim sleep10.sh
执行:jobs
结果:[1]+ 已停止 vim sleep10.sh
执行:fg #fg命令使vim sleep10.sh作业重新转到前台

 

在“Ctrl+Z”组合键之后输入bg命令可使阻塞状态的作业转入后台运行。

 

执行:./sleep20.sh
^Z #Ctrl+Z组合键
结果:[1]+ 已停止 ./sleep20.sh
执行:bg
结果:[1]+ ./sleep20.sh &
执行:jobs
结果:[1]+ 运行中 ./sleep20.sh &
[1]+ 完成 ./sleep20.sh

 

注意,fg、bg和jobs命令只能以作业号为参数来指定作业,这三个命令是不能使用进程号的。而kill、disown和wait命令既能以作业号指定作业,也可以用进程号指定作业。

 

disown命令用于从Shell的作业表中删除作业,作业表就是由jobs命令所列出的作业列表,disown可以指定删除作业表中的作业。

 

disown命令的用法:
执行:vi input &
结果:[1] 2856
执行:vi loggg &
结果:[2] 2857

 

[1]+ 已停止 vim input
执行:jobs
结果:[1]- 已停止 vim input
[2]+ 已停止 vim loggg
执行:disown %- #删除最近第2个提交的作业
结果:bash: 警告:删除进程组 2856 中已停止的任务 1
#Shell提示已经删除进程号为2856的作业,即最近第2个提交的vi input作业
执行:disown 2857 #以进程号的方式指定所要删除的作业
结果:bash: 警告:删除进程组 2857 中已停止的任务 2
#Shell提示已经删除进程号为2857的作业
执行:jobs #作业列表已经为空
结果:

 

wait命令用于等待后台作业完成。

 

例:wait命令的用法
#!/bin/bash
ls /etc | grep "rc[0-9]" &
echo "The Scirpt quits now!"
wait
执行:./backls.sh #将wait命令注释掉后的运行结果
结果:The Scirpt quits now! #打印完这行语句后,脚本就已经结束
rc0.d #但是后台的ls作业仍然在运行
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d
#输入Enter键退出
执行:./backls.sh #wait命令存在时的运行结果
结果:The Scirpt quits now!
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d #脚本等待后台的ls作业运行结束后才结束

 

12.3.3、信号
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号事件的发生有两个来源:硬件来源和软件来源。信号是进程间通信机制中唯一的异步通信机制,可以看做是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,除了基本通知功能外,还可以传递附加信息。

 

向进程发送信号大多通过“Ctrl”键加上一些功能键来实现的。
Ctrl组合键、信号类型及其意义:
Ctrl+C INT信号,即interrupt信号 停止当前运行的作业
Ctrl+Z TSTP信号,即terminal stop信号 使当前运行的作业暂时停止(进入阻塞状
态)
Ctrl+\ QUIT信号 Ctrl+C的强化版本,当Ctrl+C无法停止
作业时,使用此组合键
Ctrl+Y TSTP信号,即terminal stop信号 当进程从终端读取输入数据时,暂时停止
该进程

 

“Ctrl+Z”组合键实际上与“Ctrl+Y”是类似的,都是向进程发送TSTP信号,表示将进程暂时停止,但是它们的区别在于:“Ctrl+Y”组合键仅可以在进程从终端读取输入数据时,暂时停止该程序,而“Ctrl+Z”组合键则可以随时暂时停止进程。

 

除了利用组合键发送信号之外,内建命令kill可用于向进程发送TERM(即terminal)信号,功能与INT信号类似,也用于停止进程。而kill命令可以通过进程号、作业号或进程命令名向任何作业发送信号。

 

kill命令的用法:
执行:./sleep20.sh &
结果:[1] 6101
执行:kill %1 #通过作业号杀死进程
jobs
结果:[1]+ 已终止 ./sleep20.sh

 

例:kill命令通过用进程号杀掉自己的进程的用法
#!/bin/bash
kill $$ #位置参数$$表示本身的进程号
echo "Does this line appear?"
结果:已终止 #一运行脚本就终止了
执行:echo $?
结果:143
退出码143大于128表示脚本是被系统强行结束的,然而,该脚本的退出码还有其他含义的:当Shell脚本收到信号时,退出码是128+N,N是该脚本所收到信号的标号,此时,脚本收到的是TERM信号,TERM信号的标号正好是15。
Kill -l命令还可以列出kill命令能发出的所有信号及其标号。

 

12.3.4、trap命令
trap是Linux的内建命令,它用于捕捉信号,trap命令可以指定收到某种信号时所执行的命令。trap命令的格式为:trap command sig1 sig2 ... sigN
该格式表示当trap命令收到sig1 sig2 ... sigN中任意一个信号时,执行command命令,command命令完成后,脚本继续收到信号前的操作,直到脚本执行结束。

 

例:trap命令捕捉INT信号的用法
#!/bin/bash
#一旦收到INT信号,执行双引号内的echo命令
trap "echo 'You hit CONTROL+C!'" INT
while :; do #使用冒号表示永真,无限循环
let count=count+1 #记录进入循环的次数
echo "This is the $count sleep"
sleep 5 #每次循环休眠5秒
done
结果:This is the 1 sleep
This is the 2 sleep
^CYou hit CONTROL+C! #第1次输入“Ctrl+C”
This is the 3 sleep #第2次休眠停止,立即进入第3次休眠
This is the 4 sleep
^CYou hit CONTROL+C! #第2次输入“Ctrl+C”
This is the 5 sleep #第4次休眠停止,立即进入第5次休眠
This is the 6 sleep
^Z #输入“Ctrl+Z”结束脚本
[1]+ 已停止 ./traploop.sh

 

trap命令还可以忽略某些信息,即进程收到某些信息后不做任何处理。只要将trap命令的command用空字符串代替即可(””或'')。

 

例:trap命令忽略信号的用法
#!/bin/bash
trap "" TERM INT #忽略对TERM和INT 两种信号的处理,如果还要忽略其他信
号,将它们添加到INT之后
while :; do
sleep 5
done
结果:./nokillme.sh &
[3] 6470
执行:kill %3 #试图杀死nokillme.sh进程
jobs #nokillme.sh仍然运行
结果:[3] 运行中 ./nokillme.sh &
执行:kill -9 %3 #用更为强劲的命令杀死nokillme.sh进程
jobs #nokillme.sh已杀死
结果:[3] 已杀死 ./nokillme.sh
由于kill %3命令发送的是TERM信号,脚本的trap命令忽略了对TERM信号的处理。因此,kill %3命令不能杀死nokillme.sh进程。而kill -9 %3命令kill向3号作业发送9号信号杀死进程,9号信号实际上就是KILL信号,因此,kill -9 %3命令等价于kill -KILL %3命令。

 

子Shell能继承父Shell所忽略的信号,但是,不能继承父Shell未忽略的信号。
forever.sh脚本:
#!/bin/bash
while :; do
sleep 5
done
subsig.sh脚本:
#!/bin/bash
trap "" QUIT #忽略QUIT信号
trap "echo 'You want to kill me'" TERM #捕捉到TERM信号后,打印提示信息
#TERM捕捉,但没有忽略
( #将forever.sh脚本作为子Shell,子Shell将无限休眠
./forever.sh
)
执行:./subsig.sh &
结果:[3] 6679 #返回父Shell的作业号和进程号
执行:kill -3 6679 #向父Shell发送3号信号,即QUIT信号
执行:ps -a
结果: PID TTY TIME CMD
6679 pts/0 00:00:00 subsig.sh #父Shell未退出,说明QUIT信号被忽略
6680 pts/0 00:00:00 forever.sh
6685 pts/0 00:00:00 sleep
6686 pts/0 00:00:00 ps
执行:kill -3 6680 #向子Shell发送3号信号,即QUIT信号
ps -a
结果: PID TTY TIME CMD
6679 pts/0 00:00:00 subsig.sh
6680 pts/0 00:00:00 forever.sh#子Shell也未退出,说明QUIT信号被忽略
6701 pts/0 00:00:00 sleep
6702 pts/0 00:00:00 ps
执行:kill 6679 #向父Shell发送TERM信号
ps -a
结果: PID TTY TIME CMD
6679 pts/0 00:00:00 subsig.sh #父Shell仍未被杀掉
6680 pts/0 00:00:00 forever.sh
6713 pts/0 00:00:00 sleep
6714 pts/0 00:00:00 ps
执行:kill 6680 #向子Shell发送TERM信号
结果:已终止 #子Shell立刻被终止
You want to kill me #并打印出父Shell对TERM信号的响应信息

 

[3] 退出 143 ./subsig.sh #父shell随着子Shell的终止而终止

 

 


跳板机:

[fpc@localhost script]# cat jumpserver.sh #!/bin/bash trap '' INT echo -e "\e[1;31m-----------------------------------------------------" cat <<-EOF 1)optical modem 2)master DNS 3)slave DNS 4)router 5)virtual host1 6)virtual host2 EOF echo -e "-----------------------------------------------------\e[0m" while : do echo -ne "\e[1;32mplease input one number: (1,2,3,4,5): \e[0m" read -p "" num case $num in 1) ssh fpc@192.168.6.3 ;; 2) ssh fpc@192.168.6.66 ;; 3) exit ;; 4) ssh fpc@10.10.10.254 ;; 5) ssh fpc@10.10.10.11 ;; 6) ssh fpc@10.10.10.12 ;; *) echo "input error,input again" esac done

分发公钥:

注意:

  1. 并发执行{}& ,不常用,parallel 命令来并发执行命令可以提高效率。首先,您需要确保安装了 parallel 工具
  2. shell中调用expect,EOF中必须是tab键
  3. 并发控制:文件句柄中增加$thead行
#!/bin/bash thread=10 tmp_fifofile=/tmp/$$.fifo mkfifo $tmp_fifofile exec 8<> $tmp_fifofile rm -f $tmp_fifofile for i in `seq $thread` do echo >&8 done password="Fpc11100885" user="root" >distributed_hosts rpm -q expect &>/dev/null if [ $? -eq 0 ];then echo "install expect" sudo yum install -y expect fi if [ ! -f "~/.ssh/id_rsa" ];then ssh-keygen -P "" -f ~/.ssh/id_rsa fi for host in `cat ./network_hosts` do read -u 8 { ping -c 1 -w 2 $host &>/dev/null if [ $? -eq 0 ];then echo $host >> distributed_hosts /usr/bin/expect <<-EOF spawn ssh-copy-id ${user}@$host expect { "yes/no" {send "yes\r";exp_continue} "password" {send "$password\r";exp_continue} eof {send_tty "eof"} } EOF fi echo >&8 }& wait echo "distribute finished" done

 

 

# 并发执行 for host in $hosts; do # 后台执行命令 sshpass -p "$ROOT_PASSWD" ssh -n -o StrictHostKeyChecking=no -l root$host "userdel -r $USER; useradd$USER; echo \"$INIT_PASSWD\" | passwd --stdin$USER" & sshpass -p "$INIT_PASSWD" ssh-copy-id$USER@$host & sshpass -p "$ROOT_PASSWD" ssh-copy-id root@$host & # 等待所有后台命令完成 wait # 执行其他操作 # ... done 每个 ssh 命令都被发送到后台执行,wait 命令用于等待所有后台命令完成。这种方法简单且高效,是 Bash 中实现并发执行的常见方式。

 

 传输文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@iZuf6eblqtavferh526stwZ ~]# vim transferFile.sh
 
exec 1>transfer.log 2>failed.log
transfer(){
        echo "transfer: $SHLVL"
        srcFile=$1
        dstFile=$2
        if [ ! -e "$srcFile" ];then
                echo "$srcFile文件不存在,检查文件" >&2
                exit 1
        fi
        cat hosts|while read host
        do
                echo "*********tranfer to $host*********"
                ssh -n -o   StrictHostKeyChecking=no fpc@$host  "if [ ! -e "$dstFile" ];then mkdir -p $dstFile;fi;"
                scp  -r -o StrictHostKeyChecking=no $srcFile fpc@$host:$dstFile
                if [ $? -eq 0 ];then
                        echo "transfer $srcFile to $host:$dstFile success"
                else
                        echo "transfer $srcFile to $host:$dstFile failed"
                fi
        done
}
transfer $1 $2

  

部署kafka,zookeeper: #!/usr/bin/env bash #创建用户 #exec >init_system.log 2>&1 USER=$1 ROOT_PASSWD="Fpc11100885" INIT_PASSWD="fPc11100885" SRC_DIR="/root/srcdata" DST_DIR="/opt/dstdata" JAVA_PACKAGE="jdk-8u211-linux-x64.tar.gz" ZOOKEEPER_PACKAGE="zookeeper-3.4.14.tar.gz" SCALA_PACKAGE="scala-2.13.0-M5.tgz" KAFKA_PACKAGE="kafka_2.13-3.0.0.tgz" REMOTE_INSTALL_DIR="/opt/install_dir" REMOTE_JAVA_DIR="/opt/java" REMOTE_ZOOKEEPER_DIR="/opt/zookeeper" REMOTE_SCALA_DIR="/opt/scala" REMOTE_KAFKA_DIR="/opt/kafka" #免密 remote_hosts=" 192.168.1.10 192.168.1.11 192.168.1.12" remote_exec(){ for host in $remote_hosts do for cmd in "$@" do #echo $#:$cmd ssh -o StrictHostKeyChecking=no root@$host "$cmd" & done wait echo "remote exec并发执行结束" done } remote_transfer(){ for host in $remote_hosts do echo "**********$host:remote tranfer file:************BASH_SUBSHELL:$BASH_SUBSHELL*****************SHLVL:$SHLVL*************************" ssh -n -o StrictHostKeyChecking=no root@$host "if [ ! -e \"$DST_DIR\" ];then mkdir -p $\"$DST_DIR\";fi" if [ ! -e "$SRC_DIR" -a ! -e "$SRC_DIR/${JAVA_PACKAGE}" ];then echo "检查目录或者文件是否存在" exit 1 fi for file in "$@" do echo $#:$file scp -r "$SRC_DIR"/"${file}" root@"${host}":"${DST_DIR}" & done wait echo "remote transfer并发执行结束" done } if [ -d /root/.ssh ];then rm -rf /root/.ssh;fi echo "" |ssh-keygen -t rsa -P "" for host in $remote_hosts do sshpass -p "$ROOT_PASSWD" ssh -n -o StrictHostKeyChecking=no -l root $host "userdel -r $USER;useradd $USER;echo "$INIT_PASSWD"|passwd --stdin $USER" sshpass -p "$INIT_PASSWD" ssh-copy-id $USER@$host sshpass -p "$ROOT_PASSWD" ssh-copy-id root@$host done # 关闭防火墙和firellwalld #for host in $hosts #do # ssh -o StrictHostKeyChecking=no root@$host "systemctl stop firewalld || systemctl disabled firewalld" # ssh -o StrictHostKeyChecking=no root@$host "setenforce 0 || sed -ir \"s#SELINUXTYPE=targeted#SELINUXTYPE=disabled#g\" /etc/selinux/config" #done cmd1="systemctl stop firewalld || systemctl disabled firewalld" cmd2="setenforce 0 || sed -ir \"s#SELINUXTYPE=targeted#SELINUXTYPE=disabled#g\" /etc/selinux/config" commands=("$cmd1" "$cmd2") #remote_exec "${cmd1}" "${cmd2}" remote_exec "${commands[@]}" #安装jdk remote_exec "yum remove java-11-openjdk* java-1.8.0-openjdk* -y" remote_exec "yum list installed | grep java &>/dev/null && echo \"卸载jdk失败\" || echo \"卸载jdk成功\" " remote_exec "if [ -e $REMOTE_INSTALL_DIR ];then rm -rf ${REMOTE_INSTALL_DIR};fi" remote_exec "mkdir -p ${REMOTE_INSTALL_DIR}" remote_exec "if [ -e $REMOTE_JAVA_DIR ];then rm -rf ${REMOTE_JAVA_DIR};fi" remote_exec "if [ -e $REMOTE_ZOOKEEPER_DIR ];then rm -rf ${REMOTE_ZOOKEEPER_DIR};fi" remote_transfer "${JAVA_PACKAGE}" remote_exec "tar xvf ${DST_DIR}/${JAVA_PACKAGE} -C ${REMOTE_INSTALL_DIR}/" remote_exec "ln -sf ${REMOTE_INSTALL_DIR}/jdk1.8.0_211 ${REMOTE_JAVA_DIR}" cat >${SRC_DIR}/export_java_env.sh <<-EOF export JAVA_HOME=${REMOTE_JAVA_DIR} export PATH=\$PATH:\${JAVA_HOME}/bin:\${JAVA_HOME}/jre EOF remote_transfer "export_java_env.sh" remote_exec "mv ${DST_DIR}/export_java_env.sh /etc/profile.d/" remote_exec "source /etc/profile.d/export_java_env.sh" remote_exec "which java;java -version" #停止旧kafka和zookeeper进程,删除zookeerp上的broker节点 remote_exec "ps -ef|grep -v grep|grep kafka|awk '{print \$2}' > /tmp/kfz_pid" remote_exec 'if [ -s /tmp/kfz_pid ];then kill -9 `cat /tmp/kfz_pid`;rm -f /tmp/kfz_pid;fi' remote_exec "${REMOTE_ZOOKEEPER_DIR}/bin/zkCli.sh rmr /brokers/ids/" remote_exec "jps|grep QuorumPeerMain|grep -v grep|cut -d\" \" -f1 > /tmp/zoo.pid;cat /tmp/zoo.pid" remote_exec 'if [ -s /tmp/zoo.pid ];then kill -9 `cat /tmp/zoo.pid`;rm -f /tmp/zoo.pid;fi ' #安装zookeeper remote_transfer "${ZOOKEEPER_PACKAGE}" remote_exec "tar xvf ${DST_DIR}/${ZOOKEEPER_PACKAGE} -C ${REMOTE_INSTALL_DIR}/" remote_exec "ln -sf ${REMOTE_INSTALL_DIR}/zookeeper-3.4.14 ${REMOTE_ZOOKEEPER_DIR}" cat > ${SRC_DIR}/hosts <<-EOF 192.168.1.10 zookeeper1 192.168.1.11 zookeeper2 192.168.1.12 zookeeper3 EOF remote_transfer "hosts" remote_exec "\mv ${DST_DIR}/hosts /etc/hosts -f" #remote_exec "bash;hostname" remote_exec "\cp -f ${REMOTE_ZOOKEEPER_DIR}/conf/{zoo_sample,zoo}.cfg" cat >${SRC_DIR}/zoo.tmp <<-EOF server.1=192.168.1.10:2888:3888 server.2=192.168.1.11:2888:3888 server.3=192.168.1.12:2888:3888 EOF remote_transfer zoo.tmp remote_exec "cat ${DST_DIR}/zoo.tmp >> ${REMOTE_ZOOKEEPER_DIR}/conf/zoo.cfg" remote_exec "if [ ! -e /tmp/zookeeper ];then mkdir /tmp/zookeeper;fi" remote_exec 'if [ `hostname` == "zookeeper1" ];then echo 1 > /tmp/zookeeper/myid;fi' remote_exec 'if [ `hostname` == "zookeeper2" ];then echo 2 > /tmp/zookeeper/myid;fi' remote_exec 'if [ `hostname` == "zookeeper3" ];then echo 3 > /tmp/zookeeper/myid;fi' remote_exec "${REMOTE_ZOOKEEPER_DIR}/bin/zkServer.sh start" remote_exec "${REMOTE_ZOOKEEPER_DIR}/bin/zkServer.sh status" sleep 10 #安装scala remote_transfer "${SCALA_PACKAGE}" remote_exec "tar xvf ${DST_DIR}/${SCALA_PACKAGE} -C ${REMOTE_INSTALL_DIR}" remote_exec "ln -sf ${REMOTE_INSTALL_DIR}/scala-2.13.0-M5 ${REMOTE_SCALA_DIR}" cat > ${SRC_DIR}/export_scala_env.sh<<-EOF export SCALA_HOME=${REMOTE_SCALA_DIR} export PATH=\$PATH:\${SCALA_HOME}/bin EOF remote_transfer "export_scala_env.sh" remote_exec "mv ${DST_DIR}/export_scala_env.sh /etc/profile.d/" remote_exec "source /etc/profile.d/export_scala_env.sh" remote_exec "which scala;scala --version" #安装kafka remote_exec "if [ -e /tmp/kafka-logs ];then rm -rf /tmp/kafka-logs;fi" remote_transfer "${KAFKA_PACKAGE}" remote_exec "tar xvf ${DST_DIR}/${KAFKA_PACKAGE} -C ${REMOTE_INSTALL_DIR}" remote_exec "ln -sf ${REMOTE_INSTALL_DIR}/kafka_2.13-3.0.0 ${REMOTE_KAFKA_DIR}" remote_exec 'if [ `hostname` == "zookeeper1" ];then sed -i "s#broker.id=0#broker.id=1#g" '"$REMOTE_KAFKA_DIR/config/server.properties;"'fi' remote_exec 'if [ `hostname` == "zookeeper2" ];then sed -i "s#broker.id=0#broker.id=2#g" '"$REMOTE_KAFKA_DIR/config/server.properties;"'fi' remote_exec 'if [ `hostname` == "zookeeper3" ];then sed -i "s#broker.id=0#broker.id=3#g" '"$REMOTE_KAFKA_DIR/config/server.properties;"'fi' remote_exec 'sed -i "/^\#listeners=/a\listeners=PLAINTEXT:\/\/`hostname -i`:9092" '"$REMOTE_KAFKA_DIR"'/config/server.properties' remote_exec 'sed -i "s#localhost:2181#192.168.1.10:2181,192.168.1.11:2181,192.168.1.12:2181#g" '"$REMOTE_KAFKA_DIR"'/config/server.properties' remote_exec "${REMOTE_KAFKA_DIR}/bin/kafka-server-start.sh -daemon ${REMOTE_KAFKA_DIR}/config/server.properties" sleep 30 #验证kafka,分布式,只要在一台执行即可 remote_exec "if [ \`hostname\` == \"zookeeper1\" ];then ${REMOTE_KAFKA_DIR}/bin/kafka-topics.sh --bootstrap-server 192.168.1.10:9092,192.168.1.11:9092,192.168.1.12:9092 --list --topic fpc |grep -v '^\$' >/tmp/topicIsAlive;fi" remote_exec "if [ -s /tmp/topicIsAlive -a \`hostname\` == \"zookeeper1\" ];then ${REMOTE_KAFKA_DIR}/bin/kafka-topics.sh --bootstrap-server 192.168.1.10:9092,192.168.1.11:9092,192.168.1.12:9092 --delete --topic fpc;fi" remote_exec "if [ \`hostname\` == \"zookeeper1\" ];then ${REMOTE_KAFKA_DIR}/bin/kafka-topics.sh --bootstrap-server 192.168.1.10:9092,192.168.1.11:9092,192.168.1.12:9092 --create --topic fpc --partitions 3 --replication-factor 3;fi" remote_exec "${REMOTE_KAFKA_DIR}/bin/kafka-topics.sh --describe --bootstrap-server 192.168.1.10:9092,192.168.1.11:9092,192.168.1.12:9092 --topic fpc"

 

 

 

集群一键启停通用脚本: 设计集群的一键启停,检测,所有主机要同时检测

1、进程起来很慢,花费30分钟后进程起来后持续5分钟挂掉(可能进程配置错误,进程所在服务器磁盘满了等)
2、进程起来很快,花费1分钟起来持续10分钟挂掉
3、进程起来很快,然后一直持续正常
4、进程起来很慢,然后一直持续正常

 

[root@localhost ~]# cat kafka_tools.sh
hosts="192.168.1.10 192.168.1.11 192.168.1.12"
stop(){
for host in $hosts
do
status $host
if [ $? -ne 0 ];then
echo "$host:Kafka is already stopped"
continue
else
ssh -o StrictHostKeyChecking=no $host "/opt/kafka/bin/kafka-server-stop.sh"
count=0
while (( $count < 5 ))
do
status $host
if [ $? -ne 0 ];then
echo "$host:Kafka is stopped"
break
fi
echo "$host:Kafka is stopping,..please wait..."
sleep 3
let count++
done
if [ $count -eq 5 ];then
echo "进程停止失败,请登录$host排查"
fi
fi

done
}

start(){
for host in $hosts
do
status $host
if [ $? -eq 0 ];then
echo "$host:Kafka is already started"
continue
else
ssh -o StrictHostKeyChecking=no $host "/opt/kafka/bin/kafka-server-start.sh -daemon /opt/kafka/config/server.properties"
count=0
while (( $count < 5 ))
do
status $host
if [ $? -eq 0 ];then
echo "$host:Kafka is started"
break
fi
echo "$host:Kafka is starting,..please wait..."
sleep 3
let count++

#放在这里也可以

#if [ $count -eq 5 ];then   
#echo "进程停止失败,请登录$host排查"
#fi
done

#放在这里也可以,因为都是上面if条件不满足的时候才自增
if [ $count -eq 5 ];then   
echo "进程停止失败,请登录$host排查"
fi

done
}

 

#要循环5次检测进程都是正常,进程才算启动起来了
status(){
sts_count=0
res_count=0
while [ $sts_count -lt 5 ]
do
ssh -o StrictHostKeyChecking=no $1 "jps|grep -w Kafka"
if [ $? -eq 0 ];then
res_count=`expr $res_count + 1`
fi
sts_count=`expr $sts_count + 1`
done

if [ $res_count -eq 5 ];then
return
else
return 100
fi
}

case $1 in
start)
start
;;
stop)
stop
;;
status)

#这里设计目的:是因为想要在start,stop里面去调用status函数,同时又能直接sh $0 status检查进程状态,所以将for循环放在这里
for host in $hosts
do
status $host
if [ $? -eq 0 ];then
echo "$host:Kafka is started"
else
echo "$host:Kafka is stopped"
fi
done
;;
*)
echo "USAGE: sh $0 (start,stop,status)"
;;
esac

 

ansible:
https://www.cnblogs.com/michael-xiang/p/10462749.html
https://getansible.com/
https://www.zsythink.net/archives/tag/ansible/page/6/
https://www.cnblogs.com/f-ck-need-u/p/7576137.html#ansible
https://www.bilibili.com/video/av33611758/?from=search&seid=7420958755659258683
https://ithelp.ithome.com.tw/users/20031776/ironman/1022
https://ansible-tran.readthedocs.io/en/latest/index.html

复杂示例#

骏马金龙 (junmajinlong.com)

写出好的 ansible-playbook 还是要多阅读优秀的 playbook,这里先列出几个可供学习的示例资源:

  • https://github.com/ansible/ansible-examples 一个面向初学者的 ansible playbook 收集仓库
  • https://galaxy.ansible.com/ui/ 这里就有很多流行的应用示例了,进阶看

列出一个感觉比较清晰简洁的例子#

Copy
. ├── LICENSE.md ├── README.md ├── group_vars │   └── all ├── hosts ├── images │   ├── check.png │   ├── nosql_primer.png │   ├── replica_set.png │   ├── scale.png │   ├── sharding.png │   └── site.png ├── playbooks │   └── testsharding.yml ├── roles │   ├── common │   │   ├── files │   │   │   ├── 10gen.repo.j2 │   │   │   ├── RPM-GPG-KEY-EPEL-6 │   │   │   └── epel.repo.j2 │   │   ├── handlers │   │   │   └── main.yml │   │   ├── tasks │   │   │   └── main.yml │   │   └── templates │   │   ├── hosts.j2 │   │   └── iptables.j2 │   ├── mongoc │   │   ├── files │   │   │   └── secret │   │   ├── tasks │   │   │   └── main.yml │   │   └── templates │   │   ├── adduser.j2 │   │   ├── mongoc.conf.j2 │   │   └── mongoc.j2 │   ├── mongod │   │   ├── files │   │   │   └── secret │   │   ├── tasks │   │   │   ├── main.yml │   │   │   └── shards.yml │   │   └── templates │   │   ├── mongod.conf.j2 │   │   ├── mongod.j2 │   │   ├── repset_init.j2 │   │   └── shard_init.j2 │   └── mongos │   ├── files │   │   └── secret │   ├── tasks │   │   └── main.yml │   └── templates │   ├── enablesharding.j2 │   ├── mongos.conf.j2 │   ├── mongos.j2 │   └── testsharding.j2 ├── site.yml └── tree.txt 21 directories, 38 files

site.yml 文件的内容如下:

Copy
--- # This Playbook would deploy the whole mongodb cluster with replication and sharding. - hosts: all roles: - role: common - hosts: mongo_servers roles: - role: mongod - hosts: mongoc_servers roles: - role: mongoc - hosts: mongos_servers roles: - role: mongos - hosts: mongo_servers tasks: - include: roles/mongod/tasks/shards.yml

 https://www.junmajinlong.com/ansible/index/


__EOF__

本文作者方鹏程
本文链接https://www.cnblogs.com/fpcbk/p/18475507.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   fangpengcheng_方鹏程  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
  1. 1 黄昏 周传雄
黄昏 - 周传雄
00:00 / 00:00
An audio error has occurred.

作词 : 陈信荣

作曲 : 周传雄

编曲 : 周传雄

制作人 : 周传雄

过完整个夏天

忧伤并没有好一些

开车行驶在公路无际无边

有离开自己的感觉

唱不完一首歌

疲倦还剩下黑眼圈

感情的世界伤害在所难免

黄昏再美终要黑夜

依然记得从你口中说出再见坚决如铁

昏暗中有种烈日灼身的错觉

黄昏的地平线

划出一句离别

爱情进入永夜

依然记得从你眼中滑落的泪伤心欲绝

混乱中有种热泪烧伤的错觉

黄昏的地平线

割断幸福喜悦

相爱已经幻灭

唱不完一首歌

疲倦还剩下黑眼圈

感情的世界伤害在所难免

黄昏再美终要黑夜

依然记得从你口中说出再见坚决如铁

昏暗中有种烈日灼身的错觉

黄昏的地平线

划出一句离别

爱情进入永夜

依然记得从你眼中滑落的泪伤心欲绝

混乱中有种热泪烧伤的错觉

黄昏的地平线

割断幸福喜悦

相爱已经幻灭

依然记得从你口中说出再见坚决如铁

昏暗中有种烈日灼身的错觉

黄昏的地平线

划出一句离别

爱情进入永夜

依然记得从你眼中滑落的泪伤心欲绝

混乱中有种热泪烧伤的错觉

黄昏的地平线

割断幸福喜悦

相爱已经幻灭

配唱制作人 : 吴佳明

钢琴 : 周传雄

吉他 : 许华强

鼓 : Gary&nbsp;Gideon

贝斯 : Andy&nbsp;Peterson

弦乐编写 : 吴庆隆

弦乐 : 孔朝晖/顾文丽/隋晶晶/梁中枢/尹淑占/王言/关旗

和声编写 : 周传雄

和声 : 周传雄

录音师 : 林世龙/沈文钏/Geoffrey Lee

混音师 : 王晋溢

录音室 : 强力/HASAYAKE/Atomic&nbsp;&amp;&nbsp;Audioplex&nbsp;(Singapore)

混音室 : 白金

OP : Sony/ATV&nbsp;Music&nbsp;Publishing&nbsp;Taiwan/哈萨雅琪有限公司

SP : Sony/ATV&nbsp;Music&nbsp;Publishing&nbsp;Taiwan​

点击右上角即可分享
微信分享提示