• Spark源码分析之Spark Shell(下)


    继上次的Spark-shell脚本源码分析,还剩下后面半段。由于上次涉及了不少shell的基本内容,因此就把trap和stty放在这篇来讲述。

    上篇回顾:Spark源码分析之Spark Shell(上)

    function main() {
      if $cygwin; then
        # Workaround for issue involving JLine and Cygwin
        # (see http://sourceforge.net/p/jline/bugs/40/).
        # If you're using the Mintty terminal emulator in Cygwin, may need to set the
        # "Backspace sends ^H" setting in "Keys" section of the Mintty options
        # (see https://github.com/sbt/sbt/issues/562).
        stty -icanon min 1 -echo > /dev/null 2>&1
        export SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Djline.terminal=unix"
        "${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"
        stty icanon echo > /dev/null 2>&1
      else
        export SPARK_SUBMIT_OPTS
        "${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"
      fi
    }
    
    # Copy restore-TTY-on-exit functions from Scala script so spark-shell exits properly even in
    # binary distribution of Spark where Scala is not installed
    exit_status=127
    saved_stty=""
    
    # restore stty settings (echo in particular)
    function restoreSttySettings() {
      stty $saved_stty
      saved_stty=""
    }
    
    function onExit() {
      if [[ "$saved_stty" != "" ]]; then
        restoreSttySettings
      fi
      exit $exit_status
    }
    
    # to reenable echo if we are interrupted before completing.
    trap onExit INT
    
    # save terminal settings
    saved_stty=$(stty -g 2>/dev/null)
    # clear on error so we don't later try to restore them
    if [[ ! $? ]]; then
      saved_stty=""
    fi
    
    main "$@"
    
    # record the exit status lest it be overwritten:
    # then reenable echo and propagate the code.
    exit_status=$?
    onExit
    

    总结一下,上面的代码大体上做了三件事:

    • 1 捕获终端信号,执行退出方法,恢复一些操作
    • 2 保存终端配置,当cygwin时关闭回显,之后再恢复
    • 3 执行spark-submit,调用repl.Main

    下面我们就循序渐进学习下这半段脚本涉及的内容:

    什么是trap

    trap命令支持捕获特定的信号,然后执行某个命令。常用的用法有:

    trap "commands" signal-list 捕获到特定的信号,执行commands命令
    trap signal-list 捕获特定的信号,停止当前进程
    trap " " signal-list 捕获特定的信号,什么也不做
    

    支持的信号,可以利用kill- l查询到:

    [root@localnode3 test]# kill -l
     1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
     6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
    11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
    16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
    21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
    26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
    31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
    38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
    43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
    48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
    53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
    58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
    63) SIGRTMAX-1  64) SIGRTMAX
    
    SIGHUP     终止进程     终端线路挂断
    SIGINT    终止进程     中断进程
    SIGQUIT   建立CORE文件 终止进程,并且生成core文件
    SIGILL    建立CORE文件      非法指令
    SIGTRAP   建立CORE文件       跟踪自陷
    SIGBUS    建立CORE文件      总线错误
    SIGSEGV   建立CORE文件       段非法错误
    SIGFPE    建立CORE文件      浮点异常
    SIGIOT    建立CORE文件      执行I/O自陷
    SIGKILL   终止进程     杀死进程
    SIGPIPE   终止进程     向一个没有读进程的管道写数据
    SIGALARM  终止进程     计时器到时
    SIGTERM   终止进程     软件终止信号
    SIGSTOP   停止进程     非终端来的停止信号
    SIGTSTP   停止进程     终端来的停止信号
    SIGCONT   忽略信号     继续执行一个停止的进程
    SIGURG    忽略信号    I/O紧急信号
    SIGIO     忽略信号    描述符上可以进行I/O
    SIGCHLD   忽略信号     当子进程停止或退出时通知父进程
    SIGTTOU   停止进程     后台进程写终端
    SIGTTIN   停止进程     后台进程读终端
    SIGXGPU   终止进程     CPU时限超时
    SIGXFSZ   终止进程     文件长度过长
    SIGWINCH  忽略信号     窗口大小发生变化
    SIGPROF   终止进程     统计分布图用计时器到时
    SIGUSR1   终止进程     用户定义信号1
    SIGUSR2   终止进程     用户定义信号2
    SIGVTALRM 终止进程     虚拟计时器到时
    

    最常用的信号有四个,SIGHUP,SIGINT,SIGQUIT,SIGTSTP,使用的时候可以简写:

    trap "" 1 2 3 24 
    或
    trap "" HUP INT QUIT TSTP 
    

    然后我们回头看看源码:

    trap onExit INT
    

    这句是说,捕获INT中断信号,然就执行onExit方法。onExit中判断是否恢复终端设置。

    exit_status=127
    saved_stty=""
    
    # restore stty settings (echo in particular)
    function restoreSttySettings() {
      stty $saved_stty
      saved_stty=""
    }
    
    function onExit() {
      if [[ "$saved_stty" != "" ]]; then
        restoreSttySettings
      fi
      exit $exit_status
    }
    

    什么是stty

    stty命令可以用来改变终端的显示,比如说关闭一些按键,开启一些特殊字符的输入等等。

    这个命令最常用的就是下面几个:

    -a,--all   以人可读的方式打印所有当前设置;-a参数比单独的stty命令输出的终端信息更详细
    -g,--save  以stty可读的方式打印当前所有设置
    -F,--file=DEVICE    打开并使用特定的设备((DEVICE)以代替标准输入(stdin)
    --help      显示帮助并退出
    --version   显示版本并退出
    
    stty  size  打印终端行数和列数
    

    我们先来试试stty size这个命令

    40 100
    

    它就是打印出来了终端显示的行数和列数。

    再看看stty -a,看看都有什么内容:

    speed 38400 baud; rows 40; columns 100; line = 0;
    intr = ^C; quit = ^; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
    swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O;
    min = 1; time = 0;
    -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts -cdtrdsr
    -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel
    -iutf8
    opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
    isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
    

    最开始speed是终端输入和输出的速度,单位是(位/秒),常用的有50、75、110、134、200、300、600、1200、1800、2400、4800、9600、19200、19.2、38400、38.4、exta 和 extb。具体还得看硬件是否支持。

    后面显示了终端的基本信息,以及一些常用的按键。比如

    intr 表示中断
    quit 表示退出
    erase 表示擦除最后一个字符
    kill 表示擦除当前航
    eof 表示文件结束
    eol 表示当前行结束
    eol2 可以设置两个字符
    swtch 表示切换外壳
    start 表示停止输出后重新开始
    stop 表示停止输出
    susp 表示终端停止
    rprnt 表示刷新当前行
    werase 表示擦除最后一个单词
    lnext 表示输入下一个字符
    flush ?????
    

    再看后面:

    min = 1; time = 0;
    

    min = 1通常与icaon搭配使用,表示一次读操作至少多少个字符
    time = 0表示读超时的时间,N/10秒。

    在后面一大堆的内容是stty支持的功能,详细的可以参考:

    控制模式

    clocal	假定一行没有调制解调器控制。
    -clocal	假定一行带有调制解调器控制。
    cread	启用接收器。
    -cread	禁用接收器。
    cstopb	每个字符选择两个停止位。
    -cstopb	每个字符选择一个停止位。
    cs5, cs6, cs7, cs8	选择字符大小。
    hup,hupcl	最后关闭时挂起拨号连接。
    -hup,-hupcl	最后关闭时不挂起拨号连接。
    parenb	启用奇偶性校验的生成和检测。
    -parenb	禁用奇偶性校验的生成和检测。
    parodd	选择奇校验。
    -parodd	选择偶校验。
    0	立即挂起电话线路。
    speed	将工作站输入和输出速度设置为指定的 speed 数(以位/秒为单位)。并不是所有的硬件接口都支持所有的速度。speed 的可能值有:50、75、110、134、200、300、600、1200、1800、2400、4800、9600、19200、19.2、38400、38.4、exta 和  extb。
    注:
    exta、19200 和19.2 是同义词;extb、38400 和38.4 是同义词。
    ispeed speed	将工作站输入速度设置为指定的 speed 数(以位/秒为单位)。并不是所有的硬件接口都支持所有的速度,而且并不是所有的硬件接口都支持该选项。speed 的可能值与speed 选项相同。
    ospeed speed	将工作站输出速度设置为指定的 speed 数(以位/秒为单位)。并不是所有的硬件接口都支持所有的速度,而且并不是所有的硬件接口都支持该选项。speed 的可能值与speed 选项相同。
    

    输入模式

    brkint	中断时发出 INTR 信号。
    -brkint	中断时不发出 INTR 信号。
    icrnl	输入时将 CR 映射为 NL。
    -icrnl	输入时不将 CR 映射为 NL。
    ignbrk	输入时忽略 BREAK。
    -ignbrk	输入时不忽略 BREAK。
    igncr	输入时忽略 CR。
    -igncr	输入时不忽略 CR。
    ignpar	忽略奇偶错误。
    -ignpar	不忽略奇偶错误。
    inlcr	输入时将 NL 映射为 CR。
    -inlcr	输入时不将 NL 映射为 CR。
    inpck	启用奇偶校验。
    -inpck	禁用奇偶校验。
    istrip	将输入字符剥离到 7 位。
    -istrip	不将输入字符剥离到 7 位。
    iuclc	将大写字母字符映射为小写。
    -iuclc	不将大写字母字符映射为小写。
    ixany	允许任何字符重新启动输出。
    -ixany	只允许 START(Ctrl-Q 按键顺序)重新启动输出。
    ixoff	当输入队列接近空或满时,发送 START/STOP 字符。
    -ixoff	不发送 START/STOP 字符。
    ixon	启用 START/STOP 输出控制。一旦启用 START/STOP 输出控制,您可以按下 Ctrl-S 按键顺序暂停向工作站的输出,也可按下 Ctrl-Q 按键顺序恢复输出。
    -ixon	禁用 START/STOP 输出控制。
    imaxbel	当输入溢出时,回送 BEL 字符并且废弃最后的输入字符。
    -imaxbel	当输入溢出时,废弃所有输入。
    parmrk	标记奇偶错误。
    -parmrk	不标记奇偶错误。
    

    输出方式

    bs0, bs1	为退格符选择延迟样式(bs0 表示没有延迟)。
    cr0, cr1, cr2, cr3	为 CR 字符选择延迟样式(cr0 表示没有延迟)。
    ff0, ff1	为换页选择延迟样式(ff0 表示没有延迟)。
    nl0, nl1	为 NL 字符选择延迟样式(nl0 表示没有延迟)。
    ofill	使用延迟填充字符。
    -ofill	使用延迟定时。
    ocrnl	将 CR 字符映射为 NL 字符。
    -ocrnl	不将 CR 字符映射为 NL 字符。
    olcuc	输出时将小写字母字符映射为大写。
    -olcuc	输出时不将小写字母字符映射为大写。
    onlcr	将 NL 字符映射为 CR-NL 字符。
    -onlcr	不将 NL 字符映射为 CR-NL 字符。
    onlret	在终端 NL 执行 CR 功能。
    -onlret	在终端 NL 不执行 CR 功能。
    onocr	不在零列输出 CR 字符。
    -onocr	在零列输出 CR 字符。
    opost	处理输出。
    -opost	不处理输出;即忽略所有其它输出选项。
    ofdel	使用 DEL 字符作为填充字符。
    -ofdel	使用 NUL 字符作为填充字符。
    tab0, tab1, tab2	为水平制表符选择延迟样式(tab0 表示没有延迟)。
    tab3	扩展制表符至多个空格。
    vt0, vt1	为垂直制表符选择延迟样式(vt0 表示没有延迟)。
    

    本地模式

    echo	回送每个输入的字符。
    -echo	不回送字符。
    echoctl	以 ^X(Ctrl-X)回送控制字符,X 是将 100 八进制加到控制字符代码中给出的字符。
    -echoctl	不以 ^X(Ctrl-X)回送控制字符。
    echoe	以“backspace space backspace”字符串回送 ERASE 字符。
    注:
    该模式不保持对列位置的跟踪,因此您可能在擦除制表符和转义序列等符号时得到意外的结果。
    -echoe	不回送 ERASE 字符,只回送退格符。
    echok	在 KILL 字符后回送 NL 字符。
    -echok	在 KILL 字符后不回送 NL 字符。
    echoke	通过擦除输出行上的每个字符,回送 KILL 字符。
    -echoke	只回送 KILL 字符。
    echonl	回送 NL 字符。
    -echonl	不回送 NL 字符。
    echoprt	以 /(斜杠)和  (反斜杠) 向后回送擦除的字符。
    -echoprt	不以 /(斜杠)和  (反斜杠) 向后回送擦除的字符。
    icanon	启用规范输入(规范输入允许使用 ERASE 和 KILL 字符进行输入行的编辑)。请参阅 AIX 5L Version 5.2 Communications Programming Concepts 中的 Line Discipline Module (ldterm) 中关于canonical mode input 的讨论。
    -icanon	禁用规范输入。
    iexten	指定从输入数据中识别实现性定义的功能。要识别以下控制字符,需要设置 iexten:eol2、dsusp、reprint、discard、werase、lnext。与这些模式关联的功能也需要设置iexten:imaxbel、echoke、echoprt、echoctl。
    -iexten	指定从输入数据中识别实现性定义的功能。
    isig	启用对特殊控制字符(INTR、SUSP 和 QUIT)的字符检查。
    -isig	禁用对特殊控制字符(INTR、SUSP 和 QUIT)的字符检查。
    noflsh	不清除 INTR、SUSP 或 QUIT 控制字符之后的缓冲区。
    -noflsh	清除 INTR、SUSP 或 QUIT 控制字符之后的缓冲区。
    pending	下次读操作暂挂或输入到达时,要重新输入从原始模式转换为规范模式后被暂挂的输入。暂挂是一个内部状态位。
    -pending	没有文本暂挂。
    tostop	为背景输出发出 SIGTOU 信号。
    -tostop	不为背景输出发出 SIGTOU 信号。
    xcase	在输入中回送大写字符,并在输出显示的大写字符之前加上  (反斜杠)。
    -xcase	不在输入时回送大写字符。
    

    硬件流量控制模式

    这些选项是对 《X/Open 可移植性指南,发行版 4》 标准的扩展。
    
    cdxon	输出时启用 CD 硬件流量控制模式。
    -cdxon	输出时禁用 CD 硬件流量控制模式。
    ctsxon	输出时启用 CTS 硬件流量控制模式。
    -ctsxon	输出时禁用 CTS 硬件流量控制模式。
    dtrxoff	输入时启用 DTR 硬件流量控制模式。
    -dtrxoff	输入时禁用 DTR 硬件流量控制模式。
    rtsxoff	输入时启用 RTS 硬件流量控制模式。
    -rtsxoff	输入时禁用 RTS 硬件流量控制模式。
    

    组合模式

    cooked	请参阅 -raw 选项。
    ek	分别将 ERASE 和 KILL 字符设置为 Ctrl-H 和 Ctrl-U 按键顺序。
    evenp	启用 parenb 和 cs7。
    -evenp	禁用 parenb 并设置 cs8。
    lcase,LCASE	设置 xcase,iuclc 和olcuc。在工作站只以大写字符使用。
    -lcase,-LCASE	设置 -xcase、-iuclc 和-olcuc。
    nl	设置 -icrnl 和-onlcr。
    -nl	设置 icrnl、 onlcr、-inlcr、-igncr、-ocrnl和-onlret。
    oddp	启用 parenb、 cs7 和 parodd。
    -oddp	禁用 parenb 并设置 cs8。
    parity	请参阅 evenp 选项。
    -parity	请参阅 -evenp 选项。
    sane	将参数重新设置为合理的值。
    raw	允许原始模式输入(不包括输入处理,例如 erase、kill 或 interrupt);传回奇偶(校验)位。
    -raw	允许规范输入方式。
    tabs	保留制表符。
    -tabs,tab3	打印时将制表符替换为空格。
    窗口大小	 
    cols n,columns n	将终端(窗口)大小记录为有 n 列。
    rows n	将终端(窗口)大小记录为有 n 行。
    size	将终端(窗口)大小打印到标准输出(先是行,再是列)中。
    

    stty的小栗子

    看完上面的东西,很多人都蒙B了,这么多东西咋用啊?咱们来个小栗子,体验一下stty的奇妙。

    场景,当你远程ssh机器的时候是不是要输入密码?但是输入的密码是看不到的,这是怎么做到的?先来看看shell脚本吧!

    #!/bin/bash
    PASSWD="123"
    USER=`whoami`
    
    # save current stty setting
    SAVEDSTTY=`stty -g`
    
    # hide input characters
    stty -echo
    
    echo -n "Please input passwd:"
    read passwd
    echo ""
    if [ "$PASSWD" = "$passwd" ];then
            echo "Welcome $USER"
    else
            echo "Sorry"
    fi
    
    # echo input caharacters
    stty echo
    
    # restore stty
    stty=$SAVEDSTTY
    

    脚本的意思是:先关闭屏幕回显,即你输入啥屏幕也不显示了;然后提示输出密码;验证密码是否正确给予反馈;打开回显;恢复终端设置。

    看看效果哈:

    [root@localnode3 test]# sh stty.sh
    Please input passwd:
    Sorry
    [root@localnode3 test]# sh stty.sh
    Please input passwd:
    Welcome root
    [root@localnode3 test]#
    

    挺有意思吧!

    回头看源码

    有了对stty的了解后,回头我们看看spark-shell脚本,就清晰明了了。

    saved_stty=$(stty -g 2>/dev/null)
    

    首先保存了当前的终端配置。

    if [[ ! $? ]]; then
      saved_stty=""
    fi
    

    如果收到退出命令,就恢复stty状态。

    然后调用main方法,并传递所有的参数main "$@",最后根据返回状态,判断是直接终端退出还是恢复之前的终端界面。

    回头再看看main方法就容易理解了:

    function main() {
      if $cygwin; then
        stty -icanon min 1 -echo > /dev/null 2>&1
        export SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Djline.terminal=unix"
        "${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"
        stty icanon echo > /dev/null 2>&1
      else
        export SPARK_SUBMIT_OPTS
        "${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"
      fi
    }
    

    如果是cygwin,先关闭echo回显,设置读操作最少1个字符。然后启动spark-submit 执行org.apache.spark.repl.Main类,并设置应用的名字,传递参数。执行完成后,再开启echo回显。

    参考

  • 相关阅读:
    json_encode([0])
    try catch throw
    如何 lookback
    协程||进程
    客户错了?
    循环 php 变量会影响到传入的参数
    csv变成xls, csv乱码
    虚拟机不能git push
    数组下标大小写
    php 静态方法
  • 原文地址:https://www.cnblogs.com/xing901022/p/6415289.html
Copyright © 2020-2023  润新知