本文的目的是提升linux shell腳本的功力,以及熟悉spark-submit提交的具體流程
spark-sumbit*
#!/usr/bin/env bash
if [ -z "${SPARK_HOME}" ]; then
source "$(dirname "$0")"/find-spark-home
fi
# disable randomized hash for string in Python 3.3+
export PYTHONHASHSEED=0
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"
第一段
if [ -z "${SPARK_HOME}" ]; then
source "$(dirname "$0")"/find-spark-home
fi
背景知識
-
雙引號
"${SPARK_HOME}" 與 ${SPARK_HOME} 這樣寫是等效的,均代表獲取環境變量中的SPARK_HOME所引用的值
在shell中 “$USER” 與 $USER 其代表的值是一樣的。 加不加並不會記錄到變量中,也不會添加到字符串中,其代表的一個根本的含義就是 定義 變量的意思。默認情況下,在shell編程中,不帶雙引號的都會被當作字符串來處理。這個慣例比較特殊。反正與一般意義上的高級編程語言不太一致。(讀者可以自己揣摩一下單引號會得出什麼樣的結論)
#!/bin/bash if [ $USER = "$USER" ];then echo "eq" # 執行eq else echo "neq" fi
-
if(空格)[(空格)-[a-z](空格)(string)(空格)];then 語法
這個是常見的shell 字符串條件判斷的格式,-z代表不存在,請注意空格是必填的,這個是跟處理shell腳本的編譯器設置的語法解析器有關,或者說內置的正則表達式就是這麼獲取條件匹配的
在方括號內退出碼爲0(exit 0即正常退出)則執行then後面的語句,否則執行其他分支homewell:/home/hadoop/shareh$ cat /etc/passwd | grep abc homewell:/home/hadoop/shareh$ echo $? 1
比較其原型 是test命令
-
source命令(直接copy命令)
有人說是點(.)命令,也就是執行目標文件中的命令,但是source 不會執行生成子shell(概念要理清,這個子shell代表這創建一個新的進程被父進程管理環境變量)
如果讀者學過C++或者C,都會接觸宏(#define)關鍵字,代表者代碼在編譯的時候直接把代碼copy到引用了宏定義的代碼段中,或者類似於C++中的內聯函數,都是代碼的直接copy.可以看 綜合案例
因此可以使用另一個腳本中暴露(export)的變量,因爲相當於把另一腳本中的代碼,直接copy到當前腳本中
-
dirname 命令
獲取指定虛擬目錄的父目錄
homewell:/home/hadoop/shareh$ dirname /home/hadoop/shareh/ /home/hadoop homewell:/home/hadoop/shareh$ dirname /home/hadoop/shareh/abc abc/ abcd/ abc.sh homewell:/home/hadoop/shareh$ dirname /home/hadoop/shareh/abc.sh /home/hadoop/shareh
-
位置參數
$0是調用程序的時候第一個字符串,通常與程序名一致,往往有些博客簡單地把它理解成程序名,這樣理解是很受傷的,很容易誤傷,針對這種情況。希望讀者能區分不同場景來理解(查看本案例)
$1是第一個參數,$2是第二個參數
5.案例
#!/bin/bash echo "\$0:""$0" echo "\$1:""$1" # 超過10個參數的用{}包裹起來,否則 $10 == "$1" + "0" echo "第十個參數""${10}"
# 第一 全路徑方式 homewell:~$ /home/homewell/shell1/testdoller.sh 23 $0:/home/homewell/shell1/testdoller.sh $1:23 # 全局變量方式(同上是一致的) homewell:~/shell1$ export SHELLTEST_HOME="/home/homewell/shell1" homewell:~/shell1$ export PATH="$SHELLTEST_HOME:$PATH" homewell:~/shell1$ testdoller.sh 1235 $0:/home/homewell/shell1/testdoller.sh $1:1235 # 第二種 通過相對路徑1 homewell:~$ shell1/testdoller.sh 124 $0:shell1/testdoller.sh $1:124 # 第二種 通過相對路徑2 homewell:~$ ./shell1/testdoller.sh 124 $0:./shell1/testdoller.sh $1:124 # 第三種 直接以./方式執行 homewell:~$ cd shell1/ homewell:~/shell1$ ./testdoller.sh 125 $0:./testdoller.sh $1:125 # 第四種 通過sh 命令執行 homewell:~/shell1$ sh testdoller.sh 126 $0:testdoller.sh $1:126
擴展內容 其他內置的跟$有關的位置參數
綜合案例
-rwxr-xr-x 1 zhangll zhangll 184 10月 2 21:14 pid2.sh*
-rwxrwxrwx 1 zhangll zhangll 1292 10月 2 21:40 pid.sh*
pid.sh
#!/bin/bash
表示當前shell
echo "pid.sh當前shell pid進程(\$$):$$"
echo "pid.sh當前shell最近一個後臺進程pid(\$!):" "$!"
echo "pid.sh前面一個命令的退出碼 (\$?) : $?"
# 執行當前的目錄路徑下的pid2.sh
echo "-------------./pid2.sh--------------"
./pid2.sh
echo "pid.sh當前進程打印 pid2中的pid2暴露的變量(\$PID2): $PID2"
echo "-------------查看(子進程)的一些特性------------"
# 向外暴露a變量,在父進程中無法查看,只有在子shell中能看
export a=1
echo "pid.sh當前shell顯示a 變量(\$a):"$a
(export b=2; echo "子進程查看父進程暴露的(a):""$a";echo "子進程暴露的變量(\$b):""$b";echo "子進程打印的進程(\$\$):""$$";echo "子進程(\$BASHPID):$BASHPID")
echo "pid.sh 在 (子進程之後)當前shell最近一個後臺進程pid2(\$!):" "$!"
echo "pid.sh當前shell顯示子進程暴露的b變量(\$b) : $b"
echo "------------(./pid2.sh &)------------"
./pid2.sh &
echo "pid.sh 在 (./pid2.sh &)最近一個後臺進程pid2(\$!):" "$!"
echo "pid.sh當前進程打印 pid2中的pid2暴露的變量(\$PID2): $PID2"
echo "-----------source pid2.sh -----------"
source ./pid2.sh
echo "pid.sh source pid2.sh 之後當前進程打印 pid2中的pid2暴露的變量(\$PID2): $PID2"
exit 0
pid2.sh
#!/bin/bash
echo "######pid2.sh 打印的 (\$\$):$$"
echo "######pid2.sh 打印的 (\$BASHPID):$BASHPID"
echo "######pid2.sh 打印的最近一個後臺進程 (\$!):$!"
export PID2=30
執行結果
pid.sh當前shell pid進程($$):9553
pid.sh當前shell最近一個後臺進程pid($!):
pid.sh前面一個命令的退出碼 ($?) : 0
-------------./pid2.sh--------------
######pid2.sh 打印的 ($$):9554
######pid2.sh 打印的 ($BASHPID):9554
######pid2.sh 打印的最近一個後臺進程 ($!):
pid.sh當前進程打印 pid2中的pid2暴露的變量($PID2):
-------------查看(子進程)的一些特性------------
pid.sh當前shell顯示a 變量($a):1
子進程查看父進程暴露的(a):1
子進程暴露的變量($b):2
子進程打印的進程($$):9553
子進程($BASHPID):9555
pid.sh 在 (子進程之後)當前shell最近一個後臺進程pid2($!):
pid.sh當前shell顯示子進程暴露的b變量($b) :
------------(./pid2.sh &)------------
######pid2.sh 打印的 ($$):9556
######pid2.sh 打印的 ($BASHPID):9556
######pid2.sh 打印的最近一個後臺進程 ($!):
pid.sh 在 (./pid2.sh &)最近一個後臺進程pid2($!): 9556
pid.sh當前進程打印 pid2中的pid2暴露的變量($PID2):
-----------source pid2.sh -----------
######pid2.sh 打印的 ($$):9553
######pid2.sh 打印的 ($BASHPID):9553
######pid2.sh 打印的最近一個後臺進程 ($!):9556
pid.sh source pid2.sh 之後當前進程打印 pid2中的pid2暴露的變量($PID2): 30
可以發現
- $! 代表當前shell進程空間最近一個執行的後臺進程的id,並不會獲取父進程空間的最近一次後臺進程pid
後臺進程與子進程的區別 - ()括號包裹的雖然是開啓了一個新的空間,但是其進程id與父親的一致,但是在其內暴露(export)的變量,父進程無法獲取
- $$ 並非代表當前shell的pid,
解讀
不管以./spark-submit 還是 直接執行spark-submit,當不存在SPARK_HOME環境變量的時候,就執行當前目錄(spark-submit所在目錄)中的find-spark-home命令
很顯然find-spark-home 要做的工作肯定是設置一個 SPARK_HOME 環境變量用來給後期使用
第二段
# disable randomized hash for string in Python 3.3+
export PYTHONHASHSEED=0
背景知識
-
export 暴露/導出
該關鍵詞在nodejs/js/c/c++中會經常出現,代表這給調用該腳本的環境(腳本/shell環境)暴露一個變量,這個變量將會在當前進程空間中使用,包括子進程空間,但是不會影響父進程空間的變量
解讀
很顯然暴露了一個PYTHONHASHSEED (python hash seed)
這個是設置一個python隨機
中的seed值,當改值爲0的時候,代表着關閉隨機hash種子,意味着對相同字符串多次使用hash函數,得到的是相同的值,這裏可能考慮到pyspark上會調用hash值產生不必要的麻煩,比如spark中的group by 算子,由於可能數據在不同節點上,因此不同節點上會啓動不同的python環境(可以理解成python session),python3.2.3之後就默認開啓python環境的hash值,這意味着不同環境下的相同字符串,會默認在字符串前面添加一個鹽值,防止DOS拒絕服務攻擊手段,防止鏈表太長,降低性能。然後在group by 的相同字符串可能會在不同機器上輸出不同 內容,因此爲了能夠保證數據一致性,需要關閉這個值
第三段
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"
背景知識
-
exec 命令 參數
代表當前命令與參數 直接覆蓋當前shell內容,這個是最暴力的宏替換,不過會暴露當前的用戶變量(局部變量)與全局變量
-
exec 永久重定向操作
-
雙引號
同第一段 1)
解讀
因此第三段的意思就是使用spark_home/bin/spark-class 並且傳遞兩個參數
第一個是 org.apache.spark.deploy.SparkSubmit
第二個是 ""來代替。$@ 代表着把輸入參數作爲多個個體存在,而 $ 可以作爲一個單獨的個體存在
總結語
紙上得來終覺淺,絕知此事要躬行。簡簡單單3句話,其涵蓋的內容,不是一兩句話能夠得出來的。當你用心去體會的時候,你會發現,原來一切都這麼簡單明白