ro.py 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569
  1. #encoding=UTF-8
  2. #
  3. # 通过expect执行远程命令或传输文件
  4. #
  5. #python zo.py action=local cmds=?
  6. #python zo.py action=remote host=? username=? password=? cmds=?
  7. #python zo.py action=fget host=? username=? password=? local=? remote=?
  8. #python zo.py action=fput host=? username=? password=? local=? remote=?
  9. #
  10. import os
  11. import sys
  12. import re
  13. import csv
  14. import subprocess
  15. import time
  16. import datetime
  17. import threading
  18. #this file
  19. zo_py=os.path.realpath(__file__)
  20. #获取一个新的ID
  21. HOSTIP = "127.0.0.1" if not "SSH_CONNECTION" in os.environ else os.environ["SSH_CONNECTION"].split(" ")[2]
  22. #zo_dir="/tmp/zo%s"%("%s%03d"%(time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())), datetime.datetime.now().microsecond/1000))
  23. zo_dir="/tmp/zo"
  24. os.system('''if [[ -e "'''+zo_dir+'''" ]]; then ret=0; else mkdir -p '''+zo_dir+'''; chmod a+w '''+zo_dir+'''; ret=$?; fi; exit $ret''')
  25. __tid = 0
  26. def ntid():
  27. global __tid
  28. __tid += 1
  29. nid = "%d%d"%(os.getpid(), 100000000 + __tid)
  30. return nid
  31. #输出调试信息
  32. tl_none='' #level 0 不输出任何信息
  33. tl_fatal='F' #level 1 输出严重错误信息
  34. tl_error='E' #level 2 输出一般错误信息
  35. tl_warn='W' #level 3 输出警告信息
  36. tl_status='S' #level 4 状态信息
  37. tl_info='I' #level 5 输出提示信息
  38. tl_proc='P' #level 6 输出处理流程信息
  39. tl_response='R' #level 7 输出处理过程反馈信息
  40. tl_detail='D' #level 8 输出数据处理信息
  41. tl_debug='X' #level 9 输出详细调试信息,仅用于开发期间
  42. tl_any='A' #level a 输出任何信息,仅用于开发期间
  43. TRACE="FEWSI" #默认调试信息跟踪级别,输出到console
  44. #调试信息输出格式
  45. pt_trace="^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} \[.+\] \[(.*)\] (.*)"
  46. pt_status_msg="(.*)\[([0-9]+)\]"
  47. #返回YYYY-mm-dd HH:MM:SS.sss格式的当前时间
  48. def tracetime():
  49. return "%s.%03d"%(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), datetime.datetime.now().microsecond/1000)
  50. class setting_item:
  51. def __init__(self, filename, lineno, sid, skey, sfcmd, svalue, sdesc):
  52. idkey = re.sub("\W", "_", sid)
  53. if idkey != sid:
  54. raise Exception("键名称不合法(invalid key name) %s"%(sid))
  55. self.fn = filename
  56. self.ln = lineno
  57. self.id = sid
  58. self.key = skey
  59. self.fcmd = sfcmd
  60. self.orig_value = svalue
  61. self.script = self.value_format(svalue)
  62. self.value = self.value_format(svalue)
  63. self.desc = sdesc
  64. #生成环境变量导出脚本
  65. def str_export(self):
  66. script = ""
  67. script += "export {0}_key=\"{1}\"\n".format(self.id, self.key)
  68. script += 'export {0}_name=`if [[ "${0}_name" == "" ]]; then echo \'{1}\'; else echo "${0}_name"; fi`\n'.format(self.id, self.key if self.desc == "" else self.desc.replace("'", "'\"'\"'"))
  69. script += "export {0}_filename=\"{1}\"\n".format(self.id, self.fn if self.fn is not None else "")
  70. script += "export {0}_lineno=\"{1}\"\n".format(self.id, self.ln)
  71. script += "export {0}_fcmd=\"{1}\"\n".format(self.id, self.fcmd)
  72. script += "export {0}_script='{1}'\n".format(self.id, self.value.replace("'", "'\"'\"'"))
  73. if self.key == "":
  74. #非格式化内容,直接执行或显示
  75. if self.value.strip() != "":
  76. if self.fcmd == "~":
  77. script += "eval \"${{{0}_script}}\"\n".format(self.id)
  78. else:
  79. script += "echo \"${{{0}_script}}\"\n".format(self.id)
  80. else:
  81. if self.fcmd == "~":
  82. script += "eval \"${{{0}_script}}\"\n".format(self.id)
  83. elif self.fcmd == "@":
  84. script += "{0}() {{\n eval \"${{{1}_script}}\" \n}}\n".format(self.key, self.id)
  85. script += "export {0}=`{1}`\n".format(self.id, self.key)
  86. else:
  87. script += "export {0}=`echo \"${{{0}_script}}\"`\n".format(self.id)
  88. return script
  89. #引号处理
  90. def value_format(self, value):
  91. value = value.strip()
  92. if self.key == "":
  93. return value
  94. if len(value)>1 and value[0] == '"' and value[-1] == '"':
  95. value = value[1:-1]
  96. return value
  97. class settings:
  98. base_script = "#zo script built on " + tracetime() + "\n%s"%(
  99. '''
  100. export EVAL='`'
  101. export OSNAME=`uname`
  102. export HOSTNAME=`hostname`
  103. export USERNAME=`whoami`
  104. if [[ ${OSNAME} == 'AIX' ]]; then export LANG=ZH_CN.UTF-8; else export LANG=zh_CN.utf8; fi
  105. if [[ ${OSNAME} == 'AIX' ]]; then export LC_ALL=ZH_CN.UTF-8; else export LC_ALL=zh_CN.utf8; fi
  106. export ZO_DIR='''+zo_dir+'''
  107. echo "#通用设置完成"
  108. #系统差别设置
  109. if [[ ${OSNAME} == "AIX" || ${OSNAME} == "Darwin" ]]; then
  110. usleep() {
  111. python -c "import time; time.sleep($1.0/1000000)"
  112. }
  113. fi
  114. #改变工作目录到当前脚本所在路径
  115. CUR_DIR=`pwd`
  116. echo "${CUR_DIR}"
  117. #仅支持通过SSH连接
  118. export HOSTIP=`echo ${SSH_CONNECTION} | awk -F ' ' {'print $3'}`
  119. if [[ "${HOSTIP}" == "" ]]; then
  120. HOSTIP="127.0.0.1"
  121. fi
  122. #输出信息时间,aix系统没有微秒时间,所以用python输出微秒级时间
  123. #`date "+%Y-%m-%d %H:%M:%S.%N"`
  124. p_logtime() {
  125. python -c 'import time,datetime; print("%s.%03d"%(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), datetime.datetime.now().microsecond/1000))'
  126. }
  127. replace() {
  128. local regx="$1"
  129. local rstr="$2"
  130. local gflg="$3"
  131. }
  132. #通过meval控制执行的脚本,前面发生错误,禁止后续带错运行
  133. export last_meval_script_return=0
  134. #全局错误处理
  135. export ZO_COMMAND_RUNNING=0
  136. export errline=0
  137. export errcode=0
  138. errproc() {
  139. export errline=$1
  140. export errcode=$2
  141. #只处理不在meval控制范围的脚本错误,即当前zo script自身的错误
  142. if [[ ${last_meval_script_return} == 0 ]]; then
  143. cmdscript="`echo "${ZO_SCRIPT}" | cat -n 2>&1 | head -n $errline | tail -n 1`"
  144. echo "`p_logtime` [$HOSTIP.$$] [F] error occur when eval script [$errline]: ${cmdscript} --> return $errcode"
  145. fi
  146. }
  147. trap 'errproc "$LINENO" "$?"' ERR
  148. #禁止中断
  149. trap '' INT
  150. decode_string() {
  151. python -c "
  152. import base64
  153. import sys
  154. s = sys.stdin.readline().strip()
  155. try:
  156. if s[0] == '=':
  157. print(s[1:])
  158. else:
  159. if sys.version_info.major == 3:
  160. s = bytes(s, encoding='utf8')
  161. s = base64.b64decode(s)
  162. if sys.version_info.major == 3:
  163. s = str(s, encoding='utf8')
  164. print(s)
  165. except TypeError as e:
  166. #print(e)
  167. print(s)
  168. "
  169. }
  170. encode_string() {
  171. python -c "
  172. import base64;
  173. import sys;
  174. s = sys.stdin.readline().strip()
  175. try:
  176. if s[0] == '=':
  177. print(s[1:])
  178. else:
  179. if sys.version_info.major == 3:
  180. s = bytes(s, encoding='utf8')
  181. s = base64.b64encode(s)
  182. if sys.version_info.major == 3:
  183. s = str(s, encoding='utf8')
  184. print(s)
  185. except TypeError as e:
  186. #print(e)
  187. print(s)
  188. "
  189. }
  190. decode_password() {
  191. export EXP_PASSWORD="`printf "${EXP_PASSWORD}" | decode_string`"
  192. trace X "EXP_PASSWORD=${EXP_PASSWORD}"
  193. }
  194. #特殊字符常量
  195. export newline=`printf "\\n"`
  196. export tab=" "
  197. export space=" "
  198. export quote='"'
  199. export single_quote="'"
  200. export point_quote='`'
  201. export slash='\/'
  202. export backslash='\\'
  203. export symbol_at='@'
  204. export symbol_star='*'
  205. export symbol_question='?'
  206. export semicolon=';'
  207. export dollar='$'
  208. export brace='{'
  209. export dash='-'
  210. export return_char=`printf "\\r"`
  211. #输出信息分类定义
  212. tl_fatal='F' #level 1 输出严重错误信息
  213. tl_error='E' #level 2 输出一般错误信息
  214. tl_warn='W' #level 3 输出警告信息
  215. tl_status='S' #level 4 状态信息
  216. tl_info='I' #level 5 输出提示信息
  217. tl_proc='P' #level 6 输出处理流程信息
  218. tl_response='R' #level 7 输出处理过程反馈信息
  219. tl_detail='D' #level 8 输出数据处理信息
  220. tl_debug='X' #level 9 输出详细调试信息,仅用于开发期间
  221. tl_any='A' #level a 输出任何信息,仅用于开发期间
  222. pt_trace="^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} \[.+\] \[.*\] .*"
  223. pt_status_msg=".*\[[0-9]+\]$"
  224. pt_number="^[0-9]+$"
  225. is_trace_msg() {
  226. echo "$1" | grep -E "${pt_trace}"
  227. }
  228. is_number() {
  229. echo "$1" | grep -E "${pt_number}"
  230. }
  231. is_status_msg() {
  232. echo "$1" | grep -E "${pt_status_msg}"
  233. }
  234. #状态码
  235. export status_code=0
  236. #输出信息
  237. trace() {
  238. local level="$1"
  239. local msg="$2"
  240. local flag="$3"
  241. if [[ "${level}" == "S" ]]; then
  242. #验证&修正状态信息格式
  243. if [[ "`is_status_msg "${msg}"`" == "" ]]; then
  244. if [[ "`is_number "${status_code}"`" == "" ]]; then
  245. status_code=0
  246. fi
  247. msg="${msg}[${status_code}]"
  248. fi
  249. fi
  250. if [[ "`is_trace_msg "${msg}"`" == "" ]]; then
  251. dtms=`p_logtime`
  252. #逐行格式化
  253. additioninfo=""
  254. if [[ "${flag}" != "" ]]; then
  255. additioninfo="${flag} "
  256. fi
  257. msg="`echo "${msg}" | sed -e "s/^/${dtms} [${HOSTIP}.$$] [${level}] ${additioninfo}/"`"
  258. else
  259. #截取已格式化的日志level
  260. level=`echo "${msg}" | sed -e "s/.*\[.*\] \[//" -e "s/\].*$//"`
  261. fi
  262. if [[ "${level}" == "S" ]]; then
  263. #截取状态码
  264. export status_code=`echo "${msg}" | sed -e "s/.*\[//" -e "s/\].*$//"`
  265. fi
  266. #输出所有级别信息,由python程序决定是否显示输出内容
  267. echo "${msg}"
  268. }
  269. #执行脚本,确保脚本中的语句错误不影响整体的运行
  270. evalscriptfunction() {
  271. export BASELINENO=$LINENO
  272. eval "`echo "$1"`"
  273. local ret=$?
  274. return $ret
  275. }
  276. #执行脚本,截取脚本错误信息写入指定文件
  277. evalscript() {
  278. local zo_seq_id="$1"
  279. local scmdscript="$2"
  280. local tmperrfile="$3"
  281. trap '' INT
  282. exit_in_evalscript=0
  283. #if [[ "$BASH" != "" ]]; then
  284. # #只有bash能重定义exit命令
  285. # exit() {
  286. # trace E "部署过程脚本中不能使用exit,请用return代替"
  287. # exit_in_evalscript=$1
  288. # return $1
  289. # }
  290. #fi
  291. evalscriptfunction "${scmdscript}"
  292. local ret=$?
  293. if [[ $ret == 0 && `is_number "${exit_in_evalscript}"` != "" ]]; then
  294. ret=${exit_in_evalscript}
  295. fi
  296. return $ret
  297. }
  298. #检查脚本执行中是否发生错误
  299. check_evalscript_error() {
  300. local zo_seq_id="$1"
  301. local scmdscript="$2"
  302. local tmperrfile="$3"
  303. if [[ -e "${tmperrfile}" ]]; then
  304. local errorinfo=(`cat "${tmperrfile}"`)
  305. if [[ "${errorinfo}" != "" && "${errorinfo[3]}" == "${zo_seq_id}" ]]; then
  306. trace X "errorinfo=${errorinfo[*]}"
  307. local errorline=`expr ${errorinfo[1]} - ${errorinfo[0]} - 1`
  308. local errorno="${errorinfo[2]}"
  309. if [[ "`is_number "${errorno}"`" != "" ]]; then
  310. linecode="`echo "${scmdscript}" | cat -n 2>&1 | head -n ${errorline} | tail -n 1`"
  311. trace W "命令执行中返回码不为0:[line ${errorline}: return ${errorno}] (${linecode})"
  312. local errorlinepl=`expr ${errorline} + 2`
  313. trace D "`echo "${scmdscript}" | cat -n 2>&1 | head -n ${errorlinepl} | tail -n 5 | sed -e "s/^/~ /"`"
  314. return ${errorno}
  315. else
  316. trace W "命令执行中返回错误信息:${errorinfo[*]}"
  317. trace E "返回码不是数"
  318. return 128
  319. fi
  320. fi
  321. fi
  322. return 0
  323. }
  324. #执行脚本,如果脚本执行中发生错误,后续将无法再次调用meval,除非通过ignore_previous_error忽略错误对后续脚本的影响
  325. export ZO_SEQ_ID=10000
  326. export on_error=""
  327. meval() {
  328. if [[ ${last_meval_script_return} != 0 ]]; then
  329. trace X "meval last_meval_script_return=${last_meval_script_return}"
  330. return ${last_meval_script_return}
  331. fi
  332. local scmdscript="$*"
  333. trace X "---- meval script ----"
  334. trace X "`echo "${scmdscript}" | cat -n 2>&1 | sed -e "s/^/~ /"`"
  335. trace X "---- meval run ----"
  336. export ZO_SEQ_ID=`expr ${ZO_SEQ_ID} + 1`
  337. local zo_seq_id=${ZO_SEQ_ID}
  338. local tmperrfile="'''+zo_dir+'''/zerone.errcat.$$.${zo_seq_id}.txt"
  339. rm -f "${tmperrfile}"
  340. #忽略脚本中可能发生的错误
  341. on() {
  342. if [[ "$1" == "error" ]]; then
  343. shift
  344. if [[ "$1" == "continue" || "$1" == "break" ]]; then
  345. on_error="$1"
  346. elif [[ "$1" == "" || "$1" == "report" ]]; then
  347. on_error=""
  348. elif [[ "$1" == "occur" ]]; then
  349. shift
  350. if [[ "${on_error}" == "" ]]; then
  351. echo "$*"
  352. fi
  353. else
  354. trace W "unkown command 'on error $1'"
  355. fi
  356. else
  357. trace W "unkown command 'on $*'"
  358. fi
  359. }
  360. ignore_previous_error() {
  361. export last_meval_script_return=0
  362. echo > "${tmperrfile}"
  363. }
  364. split() {
  365. awk -F "$1" "{print $"$2"}"
  366. }
  367. margs() {
  368. script=`echo "$*" | sed -e "s/$1 //"`
  369. while read LINE
  370. do
  371. s=`echo "${script}" | sed -e "s/$1/${LINE}/g"`
  372. eval "${s}"
  373. done
  374. }
  375. #保存递归运行计数
  376. local keep_running_count=${ZO_COMMAND_RUNNING}
  377. local running_count=`expr ${ZO_COMMAND_RUNNING} + 1`
  378. export ZO_COMMAND_RUNNING=${running_count}
  379. #执行脚本
  380. local on_error_pack="${on_error}"
  381. on_error=""
  382. xscmdscript="trap 'on error occur $""BASELINENO $""LINENO $""? "${zo_seq_id}" >>"${tmperrfile}"' ERR\n${scmdscript}"
  383. evalscript "${zo_seq_id}" "${xscmdscript}" "${tmperrfile}"
  384. local evalscript_return=$?
  385. on_error="${on_error_pack}"
  386. #处理错误记录
  387. #错误记录只处理一次
  388. if [[ ${last_meval_script_return} == 0 ]]; then
  389. check_evalscript_error "${zo_seq_id}" "${scmdscript}" "${tmperrfile}"
  390. local evalscript_error=$?
  391. if [[ ${evalscript_return} == 0 ]]; then
  392. evalscript_return=${evalscript_error}
  393. fi
  394. export last_meval_script_return=${evalscript_return}
  395. fi
  396. #恢复递归运行计数
  397. if [[ ${last_meval_script_return} == 0 ]]; then
  398. export ZO_COMMAND_RUNNING=${keep_running_count}
  399. fi
  400. rm -f "${tmperrfile}"
  401. trace X "---- meval end ----"
  402. trace X "return ${last_meval_script_return}"
  403. return ${last_meval_script_return}
  404. }
  405. #get script mapped by id
  406. mscript() {
  407. local scmdid="$1"
  408. local scmdscript='${'"${scmdid}"'_script}'
  409. local cmds='echo "'${scmdscript}'"'
  410. eval echo "`${cmds}`"
  411. }
  412. #call executable key,读取参数key对应脚本,并通过meval执行
  413. mcall() {
  414. local scmdid="$1"
  415. if [[ ${last_meval_script_return} != 0 ]]; then
  416. trace X "mcall ${scmdid} last_meval_script_return=${last_meval_script_return}"
  417. return ${last_meval_script_return}
  418. fi
  419. trace X "==== mcall ${scmdid} script ===="
  420. local scmdscript="`mscript "${scmdid}"`"
  421. local scmdname_key="$""{${scmdid}_name}"
  422. local scmdname=`eval echo "${scmdname_key}"`
  423. if [[ "${scmdname}" == "" ]]; then
  424. scmdname="${scmdid}"
  425. fi
  426. trace S "${scmdname}"
  427. meval "${scmdscript}"
  428. local eval_script_return=$?
  429. trace X "==== mcall ${scmdid} end ===="
  430. return ${eval_script_return}
  431. }
  432. #重定向输出到日志格式输出
  433. zo_command_log() {
  434. local scmdscript="$1"
  435. local tmpoutfile="$2"
  436. local tmpretfile="$3"
  437. local linecount=1
  438. local charcount=0
  439. local running="."
  440. local trace_level="${ZO_TRACE}"
  441. if [[ "${trace_level}" == "" ]]; then
  442. trace_level="${tl_response}"
  443. fi
  444. touch "${tmpoutfile}"
  445. local outputtime=`date +%s`
  446. while [[ "${running}" != "" ]]; do
  447. if [[ -e "${tmpretfile}" ]]; then
  448. running="-"
  449. fi
  450. #tail -n +从1开始的行号,至少会有一行内容
  451. local lines=(`tail -n +${linecount} "${tmpoutfile}" | sed -e "s/${symbol_at}/@{symbol_at}/g" | sed -e "s/${symbol_star}/@{symbol_star}/g" | sed -e "s/${symbol_question}/@{symbol_question}/g" | sed -e "s/${space}/@{space}/g" | sed -e "s/${tab}/@{tab}/g" | sed -e "s/${return_char}/@{return_char}/g" | sed -e "s/^/@{start}/g"`)
  452. local n=1
  453. for line in ${lines[@]}
  454. do
  455. line=`printf "%s" "${line}" | sed -e "s/@{start}//g" | sed -e "s/@{space}/${space}/g" | sed -e "s/@{tab}/${tab}/g" | sed -e "s/@{return_char}//g" | sed -e "s/@{symbol_star}/${symbol_star}/g" | sed -e "s/@{symbol_question}/${symbol_question}/g" | sed -e "s/@{symbol_at}/${symbol_at}/g"`
  456. xline=${line:${charcount}}
  457. if [[ ${n} == ${#lines[@]} ]]; then
  458. #最后一行,可能尚未输出完成
  459. local currenttime=`date +%s`
  460. local waittime=`expr ${currenttime} - ${outputtime}`
  461. if [[ ${waittime} -gt 1 && ${#xline} -gt 0 ]]; then
  462. trace X "等1秒未完行输出"
  463. # 输出行数不增加
  464. trace "${trace_level}" "${xline}" ">"
  465. # 输出部分信息,继续输出剩余内容
  466. charcount=`expr ${#xline} + ${charcount}`
  467. outputtime=`date +%s`
  468. fi
  469. else
  470. if [[ ${#xline} -gt 0 || ${charcount} -eq 0 ]]; then
  471. trace "${trace_level}" "${xline}" ">"
  472. fi
  473. linecount=`expr ${linecount} + 1`
  474. n=`expr ${n} + 1`
  475. charcount=0
  476. outputtime=`date +%s`
  477. fi
  478. done
  479. if [[ "${running}" == "-" ]]; then
  480. #结束
  481. running=""
  482. if [[ ${#xline} -gt 0 ]]; then
  483. #输出最后一行剩余内容
  484. trace "${trace_level}" "${xline}" ">"
  485. fi
  486. elif [[ ${n} == 1 ]]; then
  487. #没有输出
  488. usleep 50000
  489. fi
  490. done
  491. local eval_script_return=""
  492. if [[ -e "${tmpretfile}" ]]; then
  493. eval_script_return=`cat "${tmpretfile}"`
  494. else
  495. trace W "命令执行中断"
  496. eval_script_return=128
  497. fi
  498. trace X "zo_command return ${eval_script_return}"
  499. return ${eval_script_return}
  500. }
  501. #后台执行命令,重定向输出到日志格式输出
  502. export zo_command=""
  503. zo_command_eval() {
  504. if [[ "${zo_command}" != "" ]]; then
  505. #后台执行命令,环境变量无法共享输出
  506. trace F "zo_command_eval不允许嵌套"
  507. fi
  508. zo_command="$*"
  509. local tmpoutfile="'''+zo_dir+'''/zerone.stdout.$$.txt"
  510. local tmpretfile="'''+zo_dir+'''/zerone.retcode.$$.txt"
  511. rm -f "${tmpoutfile}"
  512. rm -f "${tmpretfile}"
  513. #后台执行脚本,变量变化不能共享
  514. zo_command_eval_function() {
  515. meval "${zo_command}"
  516. echo $? > "${tmpretfile}"
  517. }
  518. if [[ ${#ZO_SCRIPT} == 0 ]]; then
  519. if [[ -e "${ZO_FILE}" ]]; then
  520. export ZO_SCRIPT=`cat "${ZO_FILE}"`;
  521. else
  522. trace W "没有定义ZO_SCRIPT或ZO_FILE"
  523. fi
  524. fi
  525. #后台执行命令
  526. zo_command_eval_function 1>${tmpoutfile} 2>&1 <&0 &
  527. #处理日志输出
  528. zo_command_log "${zo_command}" "${tmpoutfile}" "${tmpretfile}"
  529. local ret=$?
  530. rm -f "${tmpoutfile}"
  531. rm -f "${tmpretfile}"
  532. zo_command=""
  533. return ${ret}
  534. }
  535. #expect script
  536. export EXP_TIMEOUT=20
  537. export EXP_PROMPT='(%|#|\\$|>)'
  538. export EXP_SCRIPT='
  539. expect {
  540. timeout { send_user "\n连接超时\n"; exit 233 }
  541. "yes/no" { send "yes\n"; exp_continue }
  542. "assword:" { send "$env(EXP_PASSWORD)\n" }
  543. eof { send_user "\n连接错误\n"; exit 232 }
  544. }
  545. expect {
  546. timeout { exp_continue }
  547. "assword:" { send_user "\n密码错误**$env(EXP_PASSWORD)**\n"; exit 231 }
  548. " --:-- ETA" {
  549. send_user "\n开始传输\n"
  550. expect {
  551. timeout { exp_continue }
  552. "100%" { send_user "\n传输完成\n" }
  553. ":" { send_user "\n正在传输\n"; exp_continue }
  554. }
  555. expect {
  556. timeout { exp_continue }
  557. eof { catch wait retval; send_user "\n退出传输连接[lindex $retval 3]\n"; exit [lindex $retval 3] }
  558. }
  559. }
  560. -re $env(EXP_PROMPT) {
  561. send "$env(REMOTE_COMMANDS)\nexit $?\n"
  562. expect {
  563. timeout { exp_continue }
  564. expect $env(EXP_INTERACT)
  565. eof { catch wait retval; send_user "\n退出命令连接[lindex $retval 3]\n"; exit [lindex $retval 3] }
  566. }
  567. }
  568. eof { catch wait retval; send_user "\n退出连接[lindex $retval 3]\n"; exit [lindex $retval 3] }
  569. }
  570. expect {
  571. timeout { exp_continue }
  572. eof { catch wait retval; send_user "\n退出不能处理的连接[lindex $retval 3]\n"; exit [lindex $retval 3] }
  573. }
  574. '
  575. exp_run() {
  576. local exp_command="$1"
  577. trace X "EXP_PROMPT='${EXP_PROMPT}'"
  578. trace X "REMOTE_COMMANDS='${REMOTE_COMMANDS}'"
  579. expect -c "
  580. #trap {} {SIGINT SIGTERM}
  581. set timeout ${EXP_TIMEOUT}
  582. spawn ${exp_command}
  583. ${EXP_SCRIPT}"
  584. ret=$?
  585. return $ret
  586. }
  587. exp_fget() {
  588. remote_file="$1"
  589. local_file="$2"
  590. if [[ "$PIPE_HOST" == "" ]]; then
  591. trace E "没有指定通道"
  592. return 22
  593. fi
  594. #上传文件
  595. export EXP_INTERACT=""
  596. export REMOTE_COMMANDS=""
  597. export EXP_HOST="${PIPE_HOST%% *}"
  598. export EXP_USERNAME="${PIPE_USERNAME%% *}"
  599. export EXP_PASSWORD="${PIPE_PASSWORD%% *}"
  600. decode_password
  601. exp_run "scp \"${EXP_USERNAME}@${EXP_HOST}:${remote_file}\" \"${local_file}\""
  602. ret=$?
  603. return $ret
  604. }
  605. exp_fput() {
  606. local_file="$1"
  607. remote_file="$2"
  608. if [[ "$PIPE_HOST" == "" ]]; then
  609. trace E "没有指定通道"
  610. return 22
  611. fi
  612. #上传文件
  613. export EXP_INTERACT=""
  614. export REMOTE_COMMANDS=""
  615. export EXP_HOST="${PIPE_HOST%% *}"
  616. export EXP_USERNAME="${PIPE_USERNAME%% *}"
  617. export EXP_PASSWORD="${PIPE_PASSWORD%% *}"
  618. decode_password
  619. exp_run "scp \"${local_file}\" \"${EXP_USERNAME}@${EXP_HOST}:${remote_file}\""
  620. ret=$?
  621. return $ret
  622. }
  623. exp_remote() {
  624. local cmds="$@"
  625. if [[ "$PIPE_HOST" == "" ]]; then
  626. trace E "没有指定通道"
  627. return 22
  628. fi
  629. #执行预处理脚本,创建工作目录
  630. trace D "远程命令预处理"
  631. if [[ "${EXP_REMOTE_SCRIPT_FILE}" == "" ]]; then
  632. EXP_REMOTE_SCRIPT_FILE="${ZO_FILE}"
  633. fi
  634. export EXP_HOST="${PIPE_HOST%% *}"
  635. export EXP_USERNAME="${PIPE_USERNAME%% *}"
  636. export EXP_PASSWORD="${PIPE_PASSWORD%% *}"
  637. local NEXT_PIPE_HOST="${PIPE_HOST:${#EXP_HOST}}"
  638. local NEXT_PIPE_USERNAME="${PIPE_USERNAME:${#EXP_USERNAME}}"
  639. local NEXT_PIPE_PASSWORD="${PIPE_PASSWORD:${#EXP_PASSWORD}}"
  640. NEXT_PIPE_HOST="${NEXT_PIPE_HOST# }"
  641. NEXT_PIPE_USERNAME="${NEXT_PIPE_USERNAME# }"
  642. NEXT_PIPE_PASSWORD="${NEXT_PIPE_PASSWORD# }"
  643. export EXP_INTERACT=""
  644. export REMOTE_COMMANDS='if [[ -e "'''+zo_dir+'''" ]]; then ret=0; else mkdir -p '''+zo_dir+'''; chmod a+w '''+zo_dir+'''; ret=$?; fi; exit $ret'
  645. decode_password
  646. exp_run "ssh ${EXP_USERNAME}@${EXP_HOST}"
  647. ret=$?
  648. if [[ $ret != 0 ]]; then
  649. return $ret
  650. fi
  651. trace D "远程命令预处理完毕"
  652. #上传文件
  653. if [[ "$PIPE_UPLOAD_FILE" != "" ]]; then
  654. if [[ "$PIPE_UPLOAD_DEST" == "" ]]; then
  655. PIPE_UPLOAD_DEST="$PIPE_UPLOAD_FILE"
  656. fi
  657. trace D "开始上传文件 {PIPE_UPLOAD_FILE} -> ${PIPE_UPLOAD_DEST}"
  658. exp_fput "${PIPE_UPLOAD_FILE}" "${PIPE_UPLOAD_DEST}"
  659. ret=$?
  660. if [[ $ret != 0 ]]; then
  661. return $ret
  662. fi
  663. trace D "文件上传完毕"
  664. fi
  665. #上传脚本
  666. trace D "上传远程脚本"
  667. exp_fput "${ZO_FILE}" "${EXP_REMOTE_SCRIPT_FILE}"
  668. ret=$?
  669. if [[ $ret != 0 ]]; then
  670. return $ret
  671. fi
  672. #执行脚本
  673. trace D "执行远程脚本,${EXP_USERNAME}@${EXP_HOST}"
  674. if [[ "${NEXT_PIPE_HOST}" == "" ]]; then
  675. export REMOTE_COMMANDS="
  676. export OSNAME=${EVAL}uname${EVAL}
  677. if [[ $""{OSNAME} == 'AIX' ]]; then export LANG=ZH_CN.UTF-8; else export LANG=zh_CN.utf8; fi
  678. if [[ $""{OSNAME} == 'AIX' ]]; then export LC_ALL=ZH_CN.UTF-8; else export LC_ALL=zh_CN.utf8; fi
  679. export PIPE_UPLOAD_FILE='"${PIPE_UPLOAD_FILE}"'
  680. export PIPE_DOWNLOAD_FILE='"${PIPE_DOWNLOAD_FILE}"'
  681. export ZO_FILE='"${EXP_REMOTE_SCRIPT_FILE}"'
  682. export CUR_DIR='''+zo_dir+'''
  683. cd $""CUR_DIR
  684. export ZO_COMMAND='${cmds//\\\'/\\\'\\\"\\\'\\\"\\\'}'
  685. export PATH=/tmp/zo:$""{PATH}
  686. bash -c 'bash "${EXP_REMOTE_SCRIPT_FILE}"'
  687. ret=$""?
  688. rm -f '"${EXP_REMOTE_SCRIPT_FILE}"'
  689. exit $""ret"
  690. else
  691. export REMOTE_COMMANDS="
  692. export OSNAME=${EVAL}uname${EVAL}
  693. if [[ $""{OSNAME} == 'AIX' ]]; then export LANG=ZH_CN.UTF-8; else export LANG=zh_CN.utf8; fi
  694. if [[ $""{OSNAME} == 'AIX' ]]; then export LC_ALL=ZH_CN.UTF-8; else export LC_ALL=zh_CN.utf8; fi
  695. export PIPE_UPLOAD_FILE='"${PIPE_UPLOAD_FILE}"'
  696. export PIPE_DOWNLOAD_FILE='"${PIPE_DOWNLOAD_FILE}"'
  697. export PIPE_HOST='"${NEXT_PIPE_HOST}"'
  698. export PIPE_USERNAME='"${NEXT_PIPE_USERNAME}"'
  699. export PIPE_PASSWORD='"${NEXT_PIPE_PASSWORD}"'
  700. export ZO_FILE='"${EXP_REMOTE_SCRIPT_FILE}"'
  701. export CUR_DIR='''+zo_dir+'''
  702. cd $""CUR_DIR
  703. export ZO_COMMAND='exp_remote ${cmds//\\\'/\\\'\\\"\\\'\\\"\\\'}'
  704. export PATH=/tmp/zo:$""{PATH}
  705. bash -c 'bash "${EXP_REMOTE_SCRIPT_FILE}"'
  706. ret=$""?
  707. rm -f '"${EXP_REMOTE_SCRIPT_FILE}"'
  708. exit $""ret"
  709. fi
  710. exp_run "ssh ${EXP_USERNAME}@${EXP_HOST}"
  711. ret=$?
  712. if [[ $ret != 0 ]]; then
  713. return $ret
  714. fi
  715. trace D "远程脚本执行完毕"
  716. trace X "PIPE_DOWNLOAD_FILE=$PIPE_DOWNLOAD_FILE"
  717. #下载文件
  718. if [[ "$PIPE_DOWNLOAD_FILE" != "" ]]; then
  719. if [[ "$PIPE_DOWNLOAD_DEST" == "" ]]; then
  720. PIPE_DOWNLOAD_DEST="$PIPE_DOWNLOAD_FILE"
  721. fi
  722. trace D "开始下载文件 ${PIPE_DOWNLOAD_FILE} -> ${PIPE_DOWNLOAD_DEST}"
  723. exp_fget "${PIPE_DOWNLOAD_FILE}" "${PIPE_DOWNLOAD_DEST}"
  724. ret=$?
  725. if [[ $ret != 0 ]]; then
  726. return $ret
  727. fi
  728. trace D "文件下载完毕"
  729. fi
  730. #执行完毕,清理临时文件(临时目录还在使用,现在还不能清理整个临时目录)
  731. return 0
  732. }
  733. fget() {
  734. remote_file="$1"
  735. local_file="$2"
  736. trace P "文件下载"
  737. export PIPE_DOWNLOAD_FILE="${remote_file}"
  738. export PIPE_DOWNLOAD_DEST="${local_file}"
  739. remote ls -l "${PIPE_DOWNLOAD_FILE}"
  740. ret=$?
  741. ls -l "${PIPE_DOWNLOAD_DEST}"
  742. if [[ $ret == 0 ]]; then
  743. trace P "文件下载完成"
  744. else
  745. trace P "文件下载失败[$ret]"
  746. fi
  747. return $ret
  748. }
  749. fput() {
  750. local_file="$1"
  751. remote_file="$2"
  752. trace P "文件上传"
  753. export PIPE_UPLOAD_FILE="${local_file}"
  754. export PIPE_UPLOAD_DEST="${remote_file}"
  755. ls -l "${PIPE_UPLOAD_FILE}"
  756. remote ls -l "${PIPE_UPLOAD_DEST}"
  757. ret=$?
  758. if [[ $ret == 0 ]]; then
  759. trace P "文件上传完成"
  760. else
  761. trace P "文件上传失败[$ret]"
  762. fi
  763. return $ret
  764. }
  765. #命令参数:
  766. # 要远程执行的命令
  767. #环境变量参数:
  768. #完整的脚本 ZO_SCRIPT
  769. #目标服务器,可以顺序指定跳板机信息,用空格分割,password中的空格引号等特殊字符需要转码
  770. # PIPE_HOST PIPE_USERNAME PIPE_PASSWORD
  771. #执行命令前上传指定文件
  772. # PIPE_UPLOAD_FILE PIPE_UPLOAD_DEST
  773. #执行命令后下载指定文件
  774. # PIPE_DOWNLOAD_FILE PIPE_DOWNLOAD_DEST
  775. remote() {
  776. local cmds="$@"
  777. trace D "远程命令执行开始"
  778. if [[ "$ZO_FILE" == "" ]]; then
  779. #本地生成脚本文件
  780. export ZO_FILE="'''+zo_dir+'''/zerone.script.$$.txt"
  781. export EXP_REMOTE_SCRIPT_FILE="'''+zo_dir+'''/zerone.script.$HOSTIP.$$.txt"
  782. echo "${ZO_SCRIPT}" > "${ZO_FILE}"
  783. else
  784. export EXP_REMOTE_SCRIPT_FILE="${ZO_FILE}"
  785. fi
  786. exp_remote "${cmds}"
  787. ret=$?
  788. #清理
  789. rm -f "${ZO_FILE}"
  790. #保留远程命令设定状态
  791. if [[ $ret == 0 ]]; then
  792. trace D "远程命令执行完成"
  793. else
  794. trace D "远程命令执行失败[$ret]"
  795. fi
  796. return $ret
  797. }
  798. ''')
  799. def __init__(self, base=None):
  800. self.items = []
  801. self.id_items = {}
  802. self.script = ""
  803. if base is not None:
  804. self.items += base.items
  805. self.base_script = settings.base_script
  806. def check_value_self_refference(self, sid, svalue):
  807. #键值引用仅支持一种形式 "${"+sid+"}"
  808. kn = svalue.find("${"+sid+"}")
  809. if kn >= 0:
  810. if sid in self.id_items:
  811. #变量引用前已经定义,修改已定义键名,原键值将被覆盖,修正键值自引用
  812. sitem = self.id_items[sid]
  813. sitem.id = "zerone_" + ntid()
  814. self.id_items[sitem.id] = sitem
  815. svalue = svalue.replace("${"+sid+"}", "${"+sitem.id+"}")
  816. else:
  817. #变量引用前没有定义
  818. svalue = svalue.replace("${"+sid+"}", "")
  819. return svalue
  820. def keep_setting(self, filename, lineno, skey, sfcmd, svalue, scomments):
  821. if skey != "":
  822. #标准键值格式
  823. sid = skey
  824. svalue = self.check_value_self_refference(sid, svalue)
  825. else:
  826. #非标准键值
  827. sid = "zerone_" + ntid()
  828. #关键字映射为 export id
  829. #runner.trace(tl_debug, skey+"='"+svalue+"'")
  830. sitem = setting_item(filename, lineno, sid, skey, sfcmd, svalue, scomments)
  831. self.items += [sitem]
  832. #覆盖保存sid对应的sitem
  833. self.id_items[sitem.id] = sitem
  834. #配置文件解析
  835. def analyze(self, filename):
  836. with open(filename, "r") as f:
  837. #读取文件
  838. st = f.read()
  839. stlines = st.splitlines()
  840. return self.analyze_lines(stlines, filename=filename)
  841. def analyze_lines(self, stlines, filename=None):
  842. #runner.trace(tl_debug, "{}".format(stlines))
  843. if len(stlines) > 0:
  844. #重置已生成脚本
  845. self.script = ""
  846. sln = 0
  847. skey = ""
  848. sflag = ""
  849. sfcmd = ""
  850. svalue = ""
  851. scomments = ""
  852. lineno = 0
  853. for line in stlines:
  854. lineno += 1
  855. if sflag == ":":
  856. #多行处理标记(以!!开始的行,!!后面的内容会被丢弃,内容中出现的!!可以通过变量定义解决)
  857. #多行累加
  858. if line.strip()[:2] == "!!":
  859. #多行结束标记
  860. self.keep_setting(filename, sln, skey, sfcmd, svalue, scomments)
  861. skey = ""
  862. sflag = ""
  863. sfcmd = ""
  864. svalue = ""
  865. scomments = ""
  866. sln = lineno + 1
  867. else:
  868. svalue += "\n" + line
  869. elif len(line.strip()) == 0:
  870. #跳过空行
  871. pass
  872. elif line.strip()[0] == '#':
  873. #注释行,作为下一键值对行的名称
  874. scomments = line.strip()[1:].strip()
  875. else:
  876. remo = re.match("\s*([^:=]*)\s*(:=?|=)([@~]?)\s*(.*)", line)
  877. if remo is not None:
  878. if svalue != "":
  879. #非格式化内容
  880. #skey == ""
  881. sfcmd = svalue[0] if len(svalue)>0 and (svalue[0] == "@" or svalue[0] == "~") else ""
  882. svalue = svalue[1:] if sfcmd != "" else svalue
  883. self.keep_setting(filename, sln, skey, sfcmd, svalue, scomments)
  884. #标准键值格式
  885. sln = lineno
  886. skey = remo.group(1)
  887. sflag = remo.group(2)[0]
  888. sfcmd = remo.group(3)
  889. svalue = remo.group(4)
  890. if sflag != ":":
  891. self.keep_setting(filename, sln, skey, sfcmd, svalue, scomments)
  892. skey = ""
  893. sflag = ""
  894. sfcmd = ""
  895. svalue = ""
  896. scomments = ""
  897. sln = lineno + 1
  898. else:
  899. #非格式化内容
  900. #与上一非格式化行合并
  901. svalue += line + "\n"
  902. if svalue != "":
  903. #非格式化内容,包括多行没有结束标记
  904. sfcmd = svalue[0] if len(svalue)>0 and (svalue[0] == "@" or svalue[0] == "~") else ""
  905. svalue = svalue[1:] if sfcmd != "" else svalue
  906. self.keep_setting(filename, sln, skey, sfcmd, svalue, scomments)
  907. #返回对象自身
  908. return self
  909. #生成环境变量导出脚本
  910. def str_export(self, exports, item, nokey_item_proc_method):
  911. if item.key == "":
  912. #非格式化内容,没有键名
  913. if nokey_item_proc_method is None:
  914. #不做任何处理
  915. return ""
  916. else:
  917. #按普通item处理
  918. pass
  919. else:
  920. #赋值脚本只保留最后一次结果
  921. if item.id in exports:
  922. return "# ${{{0}_key}}=\"{1}\"\n".format(item.id, item.key)
  923. #用最后键值设置输出
  924. item = self.id_items[item.id]
  925. #记录已导出标记
  926. exports[item.id] = item
  927. #生成导出脚本
  928. script = ""
  929. value = item.value
  930. for sid in self.id_items:
  931. if value.find("${"+sid+"}") >= 0:
  932. sitem = self.id_items[sid]
  933. script += self.str_export(exports, sitem, nokey_item_proc_method)
  934. script += item.str_export()
  935. return script
  936. #生成脚本
  937. def __str__(self, nokey_item_proc_method=None):
  938. if self.script != "":
  939. return self.script
  940. #基础脚本
  941. self.script = self.base_script
  942. self.script += '''trace X "base script done"\n'''
  943. #定义脚本
  944. exports = {}
  945. for item in self.items:
  946. self.script += self.str_export(exports, item, nokey_item_proc_method)
  947. self.script += '''trace X "extend settings done"\n'''
  948. return self.script
  949. #命令执行状态
  950. class CommandStatus:
  951. def __init__(self):
  952. self.connected = False
  953. self.code = 0
  954. self.info = ""
  955. self.warning_message=""
  956. #进程内线程间锁,日志输出与进程相关
  957. trace_stdout_lock = threading.Lock()
  958. #日志信息跟踪解析输出
  959. class CommandTracer:
  960. trace_file_locks = {}
  961. def __init__(self, status, logfile="", logfile_renew=True):
  962. self.status = status
  963. self.logfile = logfile
  964. if self.logfile is not None and self.logfile != "":
  965. if logfile_renew:
  966. with open(self.logfile, 'w') as f:
  967. f.write("")
  968. if not self.logfile in CommandTracer.trace_file_locks:
  969. CommandTracer.trace_file_locks[self.logfile] = threading.Lock()
  970. self.trace_file_lock = CommandTracer.trace_file_locks[self.logfile]
  971. else:
  972. self.trace_file_lock = None
  973. def log(self, level, msg, exp_host=None):
  974. #去掉多余的换行符
  975. if len(msg)>0 and msg[-1] == "\n":
  976. msg = msg[:-1]
  977. lines = msg.splitlines()
  978. if len(lines)>1:
  979. for line in lines:
  980. self.log(level, line, exp_host)
  981. return
  982. #强制输出标记
  983. force = level[0] == '_'
  984. if force:
  985. level = level[1:]
  986. #是否已经是trace格式
  987. remo = re.match(pt_trace, msg)
  988. if remo is not None:
  989. #已经是trace格式,取原来的level
  990. level = remo.group(1)
  991. orig_msg = remo.group(2)
  992. else:
  993. #格式化
  994. orig_msg = msg
  995. msg = "%s [%s] [%s] %s"%(tracetime(), ("%s.%d"%(HOSTIP,os.getpid())) if exp_host is None or exp_host == "" else exp_host, level, msg)
  996. #最后状态记录
  997. if level == tl_status:
  998. smremo = re.match(pt_status_msg, orig_msg)
  999. if smremo is not None:
  1000. self.status.info = smremo.group(1)
  1001. self.status.code = int(smremo.group(2))
  1002. else:
  1003. self.status.info = orig_msg
  1004. self.status.code = 0
  1005. #保留警告信息
  1006. if re.match('.*[FEW].*', level) is not None:
  1007. self.status.warning_message += "\n" + msg
  1008. #输出到console
  1009. if force or level in TRACE:
  1010. trace_stdout_lock.acquire()
  1011. try:
  1012. print(msg)
  1013. sys.stdout.flush()
  1014. finally:
  1015. trace_stdout_lock.release()
  1016. #全输出到日志文件
  1017. if self.trace_file_lock is not None:
  1018. self.trace_file_lock.acquire()
  1019. try:
  1020. with open(self.logfile, 'a') as f:
  1021. f.write(msg+"\r\n")
  1022. finally:
  1023. self.trace_file_lock.release()
  1024. class CommandRunner:
  1025. def __init__(self, logfile="", logfile_renew=False):
  1026. self.stimeout = 3600.0 #等待超时秒
  1027. self.sout = ""
  1028. self.status = CommandStatus()
  1029. self.tracer = CommandTracer(self.status, logfile, logfile_renew=logfile_renew)
  1030. #因为状态信息只有一个,所以一个CommandRunner同时只能跑一个命令
  1031. #相当于一个执行窗口
  1032. self.subprocess_lock = threading.Lock()
  1033. def trace(self, level, msg, exp_host=None):
  1034. self.tracer.log(level, msg, exp_host)
  1035. def output(self, subprocess_outfile, exp_host, silent):
  1036. with open(subprocess_outfile, 'r') as fout:
  1037. sout = fout.read()
  1038. n = len(sout) - len(self.sout)
  1039. if n>0:
  1040. if not silent:
  1041. st = sout[-n:]
  1042. st = st.replace("\r", "")
  1043. if len(st)>0 and st[-1] == "\n":
  1044. st = st[:-1]
  1045. lines = st.split("\n")
  1046. for line in lines:
  1047. if len(self.sout)>0:
  1048. self.trace(tl_detail, line, exp_host)
  1049. else:
  1050. self.trace(tl_detail, line)
  1051. self.sout += line + "\n"
  1052. self.sout = sout
  1053. return n
  1054. return 0
  1055. def subprocess(self, cmd, exp_host=None, silent=False, envs=None):
  1056. self.subprocess_lock.acquire()
  1057. subprocess_outfile = "%s/zerone.stdout.%s.txt"%(zo_dir, ntid())
  1058. try:
  1059. env = os.environ.copy()
  1060. if envs is not None:
  1061. env.update(envs)
  1062. env["ZO_SCRIPT"] = cmd
  1063. self.sout = "" #clear last sout
  1064. with open(subprocess_outfile, 'w') as fout:
  1065. child = subprocess.Popen('bash -c "${ZO_SCRIPT}"', shell=True, stdout=fout, stderr=fout, stdin=None, env=env)
  1066. timeout = time.time() + self.stimeout
  1067. while (self.stimeout<=0 or time.time()<timeout) and child.poll() is None:
  1068. if self.output(subprocess_outfile, exp_host, silent) == 0:
  1069. time.sleep(0.1) #sleep s
  1070. if child.poll() is None:
  1071. child.kill()
  1072. self.output(subprocess_outfile, exp_host, silent)
  1073. ret = child.returncode
  1074. finally:
  1075. os.remove(subprocess_outfile)
  1076. self.subprocess_lock.release()
  1077. return ret, self.sout
  1078. #执行shell脚本
  1079. def command_run(self, cmd, exp_host=None, silent=False, envs=None):
  1080. ret,sout = self.subprocess(cmd, exp_host, silent, envs)
  1081. self.trace(tl_debug, "command_run return %d"%(ret))
  1082. return ret, sout
  1083. #生成脚本
  1084. def build_script(self, host="", username="", password="", cmds="", zo_envs=None, sys_argv=None, setting_files=None):
  1085. #缺省环境设置
  1086. env_settings = settings()
  1087. if setting_files is not None:
  1088. for setting_file in setting_files:
  1089. if os.access(setting_file, os.R_OK):
  1090. env_settings.analyze(setting_file)
  1091. else:
  1092. self.trace(tl_warn, "File not found %s"%(agent_settings_file))
  1093. #命令行参数指定配置信息
  1094. #先将命令行参数中型为 key=value 的参数转换为 key:=value!! 的形式,以支持 value 为多行的情况
  1095. if sys_argv is not None and len(sys_argv)>1:
  1096. for skv in sys_argv:
  1097. n = skv.find("=")
  1098. if n >= 0 and re.match("\w+", skv[:n]):
  1099. sk = skv[:n]
  1100. sv = skv[n+1:]
  1101. env_settings.analyze_lines("{0}:={1}\n!!".format(sk, sv).splitlines())
  1102. #传递的环境变量,zo.py的嵌套调用时,py进程之间传递环境变量
  1103. zo_env_keys=[]
  1104. if "ZO_ENV_KEYS" in os.environ and os.environ["ZO_ENV_KEYS"] != "":
  1105. zo_env_keys=os.environ["ZO_ENV_KEYS"].split(",")
  1106. for envk in zo_env_keys:
  1107. if envk != "" and envk in os.environ and os.environ[envk] != "":
  1108. env_settings.analyze_lines("{0}:\n{1}\n!!".format(envk, os.environ[envk]).splitlines())
  1109. #参数指定可向下传递的环境变量
  1110. if zo_envs is not None:
  1111. for envk in zo_envs:
  1112. env_settings.analyze_lines("{0}:\n{1}\n!!".format(envk, zo_envs[envk]).splitlines())
  1113. #向下传递的环境变量
  1114. zo_env_keys += zo_envs.keys()
  1115. zo_env_keys = dict.fromkeys(zo_env_keys)
  1116. #向下传递的环境变量
  1117. env_settings.analyze_lines(["ZO_ENV_KEYS={0}".format(",".join(zo_env_keys))])
  1118. #参数指定信息
  1119. env_settings.analyze_lines([
  1120. "host=%s"%host,
  1121. "username=%s"%username,
  1122. "password=%s"%password])
  1123. #覆盖关键配置信息及待执行命令
  1124. scmdscript = str(env_settings) + ('''
  1125. trace X "specified commands begin"
  1126. trace A "${ZO_COMMAND}"
  1127. zo_command_eval "${ZO_COMMAND}"
  1128. export ret=$?
  1129. trace X "specified commands end [${ret}]"
  1130. exit $ret
  1131. ''')
  1132. return scmdscript
  1133. def run_script(self, host, username, password, cmds, sys_argv=None, envs=None):
  1134. scmdscript = self.build_script(host, username, password, cmds, sys_argv=sys_argv)
  1135. renvs={}
  1136. if envs is not None:
  1137. renvs.update(envs);
  1138. renvs["PIPE_HOST"]=host
  1139. renvs["PIPE_USERNAME"]=username
  1140. renvs["PIPE_PASSWORD"]=password
  1141. renvs["ZO_COMMAND"]=cmds
  1142. if tl_any in TRACE:
  1143. ret,sout = self.command_run('echo "${SCMDS_SCRIPT}" | cat -n', silent=True, envs={"SCMDS_SCRIPT": scmdscript})
  1144. self.trace(tl_any, sout)
  1145. ret,sout = self.command_run(scmdscript, envs=renvs)
  1146. if tl_any in TRACE and ret != 0:
  1147. with open("scmdscript.txt", 'w') as fcmds:
  1148. fcmds.write(scmdscript)
  1149. return ret,sout
  1150. #action=local cmds=?
  1151. #action=remote host=? username=? password=? cmds=?
  1152. #action=fget host=? username=? password=? local=? remote=?
  1153. #action=fput host=? username=? password=? local=? remote=?
  1154. def run(self, action="", host="", username="", password="", cmds="", local="", remote="", sys_argv=None):
  1155. self.trace(tl_debug, '''action="{0}", host="{1}", username="{2}", password="{3}", cmds="{4}", local="{5}", remote="{6}", sys_argv="{7}"'''.format(action, host, username, password, cmds, local, remote, sys_argv))
  1156. if action == "fget" and remote != "":
  1157. if local == "":
  1158. #缺省取当前目录下的同名文件
  1159. local = remote.split("/")[-1]
  1160. self.trace(tl_proc, "下载文件 %s@%s:%s 到 %s"%(username, host, remote, local))
  1161. cmds = "fget '{0}' '{1}'".format(remote, local)
  1162. ret, sout = self.run_script(host, username, password, cmds, sys_argv, envs={"ZO_TRACE": tl_detail})
  1163. elif action == "fput" and remote != "":
  1164. if local == "":
  1165. #缺省取当前目录下的同名文件
  1166. local = remote.split("/")[-1]
  1167. self.trace(tl_proc, "上传文件 %s 到 %s@%s:%s"%(local, username, host, remote))
  1168. cmds = "fput '{0}' '{1}'".format(local, remote)
  1169. ret, sout = self.run_script(host, username, password, cmds, sys_argv, envs={"ZO_TRACE": tl_detail})
  1170. else:
  1171. if action == "":
  1172. if username != "":
  1173. action = "remote"
  1174. if host == "":
  1175. #缺省连接本机
  1176. host = "127.0.0.1"
  1177. else:
  1178. action = "local"
  1179. if action == "remote":
  1180. self.trace(tl_proc, "执行远程命令 %s@%s: %s"%(username, host, cmds))
  1181. cmds = "remote '%s'"%(cmds.replace("'", "'\"'\"'"))
  1182. ret, sout = self.run_script(host, username, password, cmds, sys_argv, envs={"ZO_TRACE": tl_detail})
  1183. elif action == "local":
  1184. self.trace(tl_proc, "执行本地命令 %s"%(cmds))
  1185. ret, sout = self.run_script(host, username, password, cmds, sys_argv, envs={"ZO_TRACE": tl_response})
  1186. else:
  1187. self.trace(tl_error, "unknow action %s"%(action))
  1188. ret = 22 #Invalid argument
  1189. sout = ""
  1190. if ret is None:
  1191. #设置状态,命令执行超时,已经强制结束
  1192. self.trace(tl_status, "操作超时[230]")
  1193. ret = 230
  1194. if ret != 0:
  1195. #设置状态,代替返回错误码
  1196. errmsg = ERROR_MSG[ret].strip()
  1197. self.trace(tl_status, "%s[%d]"%(errmsg, ret), exp_host="%s.%d%s"%(HOSTIP,os.getpid(),"" if host == "" else ("--"+host)))
  1198. #ERROR_MSG_230="操作超时 "
  1199. #ERROR_MSG_231="密码错误 "
  1200. #ERROR_MSG_232="远程连接错误 "
  1201. #ERROR_MSG_233="远程连接超时 "
  1202. #ERROR_MSG_234="运行时错误 "
  1203. #ERROR_MSG_235="交互处理表达式语法错误 "
  1204. self.status.connected = (self.status.code < 230 or self.status.code >235)
  1205. if self.status.code == 0:
  1206. self.trace(tl_status, "操作完成[0]", exp_host="%s.%d%s"%(HOSTIP,os.getpid(),"" if host == "" else ("--"+host)))
  1207. return ret, sout
  1208. #命令行参数
  1209. class zoargs:
  1210. def __init__(self, argv):
  1211. global TRACE
  1212. self.host = ""
  1213. self.username = ""
  1214. self.password = ""
  1215. self.action = ""
  1216. self.cmds = ""
  1217. self.local = ""
  1218. self.remote = ""
  1219. self.TRACE = TRACE
  1220. self.logdir = ""
  1221. self.debug = ""
  1222. #parse argv
  1223. self.parse(argv)
  1224. TRACE = self.TRACE
  1225. def parse(self, argv):
  1226. cmds = None
  1227. for s in argv:
  1228. if cmds is None:
  1229. n = s.find("=")
  1230. if n >= 0:
  1231. self.__dict__[s[:n]] = s[n+1:]
  1232. elif s == "cmds":
  1233. cmds = ""
  1234. else:
  1235. self.__dict__[s] = s
  1236. elif cmds == "":
  1237. cmds = s
  1238. else:
  1239. cmds += " " + s
  1240. if cmds is not None:
  1241. self.cmds = cmds
  1242. if self.debug != "":
  1243. self.cmds = self.debug + " \"" + self.cmds.replace("'", "").replace("\"", "") + "\""
  1244. args = zoargs(sys.argv)
  1245. runner = CommandRunner(logfile=(args.logdir+"/"+args.username+"@"+args.host+".txt") if args.logdir != "" else "")
  1246. #密码加密
  1247. #python -c "import base64; print(base64.b64encode('123456'));"
  1248. #执行远程命令
  1249. #可以通过参数设置命令所需环境变量
  1250. #在指定节点上执行命令
  1251. #python zo.py TRACE=FEWSIPD action=remote host=17.192.94.65 password='QkpAbnBjMjAxOA==' cmds="ls"
  1252. #
  1253. #python zo.py action=local cmds=?
  1254. #python zo.py action=remote host=? username=? password=? cmds=?
  1255. #python zo.py action=fget host=? username=? password=? local=? remote=?
  1256. #python zo.py action=fput host=? username=? password=? local=? remote=?
  1257. def main():
  1258. #切换到当前目录的上一级目录
  1259. cur_file_dir = os.path.split(os.path.abspath(__file__))[0];
  1260. envs_work_dir = os.path.split(cur_file_dir)[0];
  1261. os.chdir(envs_work_dir);
  1262. runner.trace(tl_debug, "current work dir: "+envs_work_dir)
  1263. runner.trace(tl_debug, "ro: python '"+"' '".join(sys.argv)+"'")
  1264. runner.trace(tl_detail, "args: "+str(args.__dict__))
  1265. if args.cmds.find("exit") >= 0:
  1266. print("远程命令中不能使用exit,需要用return替代,确实需要exit可以通过变量实现!")
  1267. return 1
  1268. ret, sout = runner.run(args.action, args.host, args.username, args.password, cmds=args.cmds, local=args.local, remote=args.remote, sys_argv=sys.argv)
  1269. #不能清除临时目录,可能还在使用,os.system("rm -rf %s"%zo_dir)
  1270. return ret
  1271. ERROR_MSG = {}
  1272. ERROR_MSG[1]="Operation not permitted "
  1273. ERROR_MSG[2]="No such file or directory "
  1274. ERROR_MSG[3]="No such process "
  1275. ERROR_MSG[4]="Interrupted system call "
  1276. ERROR_MSG[5]="Input/output error "
  1277. ERROR_MSG[6]="No such device or address "
  1278. ERROR_MSG[7]="Argument list too long "
  1279. ERROR_MSG[8]="Exec format error "
  1280. ERROR_MSG[9]="Bad file descriptor "
  1281. ERROR_MSG[10]="No child processes "
  1282. ERROR_MSG[11]="Resource temporarily unavailable "
  1283. ERROR_MSG[12]="Cannot allocate memory "
  1284. ERROR_MSG[13]="Permission denied "
  1285. ERROR_MSG[14]="Bad address "
  1286. ERROR_MSG[15]="Block device required "
  1287. ERROR_MSG[16]="Device or resource busy "
  1288. ERROR_MSG[17]="File exists "
  1289. ERROR_MSG[18]="Invalid cross-device link "
  1290. ERROR_MSG[19]="No such device "
  1291. ERROR_MSG[20]="Not a directory "
  1292. ERROR_MSG[21]="Is a directory "
  1293. ERROR_MSG[22]="Invalid argument "
  1294. ERROR_MSG[23]="Too many open files in system "
  1295. ERROR_MSG[24]="Too many open files "
  1296. ERROR_MSG[25]="Inappropriate ioctl for device "
  1297. ERROR_MSG[26]="Text file busy "
  1298. ERROR_MSG[27]="File too large "
  1299. ERROR_MSG[28]="No space left on device "
  1300. ERROR_MSG[29]="Illegal seek "
  1301. ERROR_MSG[30]="Read-only file system "
  1302. ERROR_MSG[31]="Too many links "
  1303. ERROR_MSG[32]="Broken pipe "
  1304. ERROR_MSG[33]="Numerical argument out of domain "
  1305. ERROR_MSG[34]="Numerical result out of range "
  1306. ERROR_MSG[35]="Resource deadlock avoided "
  1307. ERROR_MSG[36]="File name too long "
  1308. ERROR_MSG[37]="No locks available "
  1309. ERROR_MSG[38]="Function not implemented "
  1310. ERROR_MSG[39]="Directory not empty "
  1311. ERROR_MSG[40]="Too many levels of symbolic links "
  1312. ERROR_MSG[42]="No message of desired type "
  1313. ERROR_MSG[43]="Identifier removed "
  1314. ERROR_MSG[44]="Channel number out of range "
  1315. ERROR_MSG[45]="Level 2 not synchronized "
  1316. ERROR_MSG[46]="Level 3 halted "
  1317. ERROR_MSG[47]="Level 3 reset "
  1318. ERROR_MSG[48]="Link number out of range "
  1319. ERROR_MSG[49]="Protocol driver not attached "
  1320. ERROR_MSG[50]="No CSI structure available "
  1321. ERROR_MSG[51]="Level 2 halted "
  1322. ERROR_MSG[52]="Invalid exchange "
  1323. ERROR_MSG[53]="Invalid request descriptor "
  1324. ERROR_MSG[54]="Exchange full "
  1325. ERROR_MSG[55]="No anode "
  1326. ERROR_MSG[56]="Invalid request code "
  1327. ERROR_MSG[57]="Invalid slot "
  1328. ERROR_MSG[59]="Bad font file format "
  1329. ERROR_MSG[60]="Device not a stream "
  1330. ERROR_MSG[61]="No data available "
  1331. ERROR_MSG[62]="Timer expired "
  1332. ERROR_MSG[63]="Out of streams resources "
  1333. ERROR_MSG[64]="Machine is not on the network "
  1334. ERROR_MSG[65]="Package not installed "
  1335. ERROR_MSG[66]="Object is remote "
  1336. ERROR_MSG[67]="Link has been severed "
  1337. ERROR_MSG[68]="Advertise error "
  1338. ERROR_MSG[69]="Srmount error "
  1339. ERROR_MSG[70]="Communication error on send "
  1340. ERROR_MSG[71]="Protocol error "
  1341. ERROR_MSG[72]="Multihop attempted "
  1342. ERROR_MSG[73]="RFS specific error "
  1343. ERROR_MSG[74]="Bad message "
  1344. ERROR_MSG[75]="Value too large for defined data type "
  1345. ERROR_MSG[76]="Name not unique on network "
  1346. ERROR_MSG[77]="File descriptor in bad state "
  1347. ERROR_MSG[78]="Remote address changed "
  1348. ERROR_MSG[79]="Can not access a needed shared library "
  1349. ERROR_MSG[80]="Accessing a corrupted shared library "
  1350. ERROR_MSG[81]=".lib section in a.out corrupted "
  1351. ERROR_MSG[82]="Attempting to link in too many shared libraries "
  1352. ERROR_MSG[83]="Cannot exec a shared library directly "
  1353. ERROR_MSG[84]="Invalid or incomplete multibyte or wide character "
  1354. ERROR_MSG[85]="Interrupted system call should be restarted "
  1355. ERROR_MSG[86]="Streams pipe error "
  1356. ERROR_MSG[87]="Too many users "
  1357. ERROR_MSG[88]="Socket operation on non-socket "
  1358. ERROR_MSG[89]="Destination address required "
  1359. ERROR_MSG[90]="Message too long "
  1360. ERROR_MSG[91]="Protocol wrong type for socket "
  1361. ERROR_MSG[92]="Protocol not available "
  1362. ERROR_MSG[93]="Protocol not supported "
  1363. ERROR_MSG[94]="Socket type not supported "
  1364. ERROR_MSG[95]="Operation not supported "
  1365. ERROR_MSG[96]="Protocol family not supported "
  1366. ERROR_MSG[97]="Address family not supported by protocol "
  1367. ERROR_MSG[98]="Address already in use "
  1368. ERROR_MSG[99]="Cannot assign requested address "
  1369. ERROR_MSG[100]="Network is down "
  1370. ERROR_MSG[101]="Network is unreachable "
  1371. ERROR_MSG[102]="Network dropped connection on reset "
  1372. ERROR_MSG[103]="Software caused connection abort "
  1373. ERROR_MSG[104]="Connection reset by peer "
  1374. ERROR_MSG[105]="No buffer space available "
  1375. ERROR_MSG[106]="Transport endpoint is already connected "
  1376. ERROR_MSG[107]="Transport endpoint is not connected "
  1377. ERROR_MSG[108]="Cannot send after transport endpoint shutdown "
  1378. ERROR_MSG[109]="Too many references=cannot splice "
  1379. ERROR_MSG[110]="Connection timed out "
  1380. ERROR_MSG[111]="Connection refused "
  1381. ERROR_MSG[112]="Host is down "
  1382. ERROR_MSG[113]="No route to host "
  1383. ERROR_MSG[114]="Operation already in progress "
  1384. ERROR_MSG[115]="Operation now in progress "
  1385. ERROR_MSG[116]="Stale NFS file handle "
  1386. ERROR_MSG[117]="Structure needs cleaning "
  1387. ERROR_MSG[118]="Not a XENIX named type file "
  1388. ERROR_MSG[119]="No XENIX semaphores available "
  1389. ERROR_MSG[120]="Is a named type file "
  1390. ERROR_MSG[121]="Remote I/O error "
  1391. ERROR_MSG[122]="Disk quota exceeded "
  1392. ERROR_MSG[123]="No medium found "
  1393. ERROR_MSG[124]="Wrong medium type "
  1394. ERROR_MSG[125]="Operation canceled "
  1395. ERROR_MSG[126]="权限不够(Required key not available) "
  1396. ERROR_MSG[127]="Command not found(Key has expired) "
  1397. ERROR_MSG[128]="Key has been revoked "
  1398. ERROR_MSG[129]="Key was rejected by service "
  1399. ERROR_MSG[130]="执行过程被中断(Owner died) "
  1400. ERROR_MSG[131]="State not recoverable "
  1401. ERROR_MSG[143]="主进程被杀 "
  1402. ERROR_MSG[230]="操作超时 "
  1403. ERROR_MSG[231]="密码错误 "
  1404. ERROR_MSG[232]="远程连接错误 "
  1405. ERROR_MSG[233]="远程连接超时 "
  1406. ERROR_MSG[234]="运行时错误 "
  1407. ERROR_MSG[235]="交互处理表达式语法错误 "
  1408. ERROR_MSG[239]="找不到安装介质 "
  1409. ERROR_MSG[240]="脚本执行过程中发生未知错误 "
  1410. ERROR_MSG[241]="部署依赖不正确 "
  1411. ERROR_MSG[242]="启动失败 "
  1412. ERROR_MSG[243]="返回格式不正确 "
  1413. ERROR_MSG[244]="服务器名称不符 "
  1414. ERROR_MSG[245]="卸载失败 "
  1415. ERROR_MSG[246]="参数错误 "
  1416. ERROR_MSG[247]="参数错误 "
  1417. ERROR_MSG[248]="参数错误 "
  1418. ERROR_MSG[249]="远程脚本执行错误 "
  1419. ERROR_MSG[255]="部署已存在,命令执行失败,请重试 "
  1420. ERROR_MSG[254]="安装介质已分发 "
  1421. ERROR_MSG[253]="部署存在未启动 "
  1422. ERROR_MSG[252]="未部署 "
  1423. ERROR_MSG[251]="路径存在未安装 "
  1424. ERROR_MSG[250]="status250 "
  1425. if __name__ == '__main__':
  1426. try:
  1427. exit(main())
  1428. except KeyboardInterrupt as ki:
  1429. print("")
  1430. exit(130)
  1431. # _oo0oo_
  1432. # o8888888o
  1433. # 88" . "88
  1434. # (| -_- |)
  1435. # 0\ = /0
  1436. # ___/`---'\___
  1437. # .' \\| |// '.
  1438. # / \\||| : |||// \
  1439. # / _||||| -:- |||||- \
  1440. # | | \\\ - /// | |
  1441. # | \_| ''\---/'' |_/ |
  1442. # \ .-\__ '-' ___/-. /
  1443. # ___'. .' /--.--\ `. .'___
  1444. # ."" '< `.___\_<|>_/___.' >' "".
  1445. # | | : `- \`.;`\ _ /`;.`/ - ` : | |
  1446. # \ \ `_. \_ __\ /__ _/ .-` / /
  1447. # =====`-.____`.___ \_____/___.-`___.-'=====
  1448. # `=---='
  1449. #
  1450. #
  1451. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1452. #
  1453. # 见见之时 见非是见 见犹离见 见不能及
  1454. #