runit是一个应用(application)启动管理工具。通过Procfile文件启动相应的进程。
1 试题描述
1.1 Procfile
Procfile 包含进程名字和启动进程的命令,用:分隔。如:
web: python -m SimpleHTTPServer $PORT
date: date $DATE_FORMAT
web_2: while true ; do nc -l $PORT < index.html
- 进程名字可以包含:字母, 数字, 下划线
- Procfile中不可以写后台命令
- runit将这些命令运行在后台
- runit默认使用当前路径下的Procfile文件
- 如果多次使用$PORT变量,则值递增。如第一个PORT的值是8080,则第二个PORT的值为8081,如果不在.env中设置PORT变量的值,则自动设置默认值为8080
1.2 环境变量
如果当前目录下存在.env文件,则从其中读取环境变量。这个文件由 键/值对 构成。如:
PORT=8080
DATE_FORMAT='+%Y-%m-%d|%H:%M:%S'
1.3 程序执行
- runit 启动Procfile中的所有进程
- runit -f procfile -e env_file
- runit -c 检查Procfile, env_file文件格式的正确性
- runit -h 打印帮助
1.4 其他要求
- Procfile和.env文件中可存在#注释
- usage内容第一行如下, 其余内容自由发挥
Usage: runit [-c] [-f procfile|Procfile] [-e envfile|.env]
- 日志打印到屏幕,格式如下,不同的进程的日志输出不同的颜色(web, date, web_2分别是不同的颜色)
11:39:45 web | python -m SimpleHTTPServer 8080 started with pid 781
11:39:45 date | date +%Y-%m-%d|%H:%M:%S started with pid 790
11:39:45 web_2 | nc -l 8081 < index.html started with pid 801
- runit按照Procfile的描述启动进程,例如web就是启动一个前台进程(非后台进程或daemon)
- runit可以接收SIGINT和SIGTERM信号,杀掉已启动的进程。确保runit在子进程运行结束后才退出。(如果进程可瞬间完成或是后台进程,则这个功能不起作用)
- 除grep外不允许使用其他外部命令,如:sed, awk, ps, bc
- 遵循shell编程规范
二、代码展示
#!/bin/bash
#
#Copyright (c) 2016 Baidu.com,Inc. All Right Reserved
#
#Author:panlu@baidu.com
#Date:2016/08/17
#
#Brief:
# process launcher
#Globals:
# PORT COLOR_ARRAY
#Arguments:
# -c check
# -f procfile
# -e envfile
# -h help
#Returns:
# succ:0
# fail:1
set -o pipefail
# environment
SPORT=8080
COLOR_ARRAY=('32' '33' '34' '35' '36')
# variable
procfile=""
envfile=""
#####################################
#Brief:
# usage
#Globals:
# none
#Agruments:
# none
#Returns:
# none
#####################################
function usage() {
echo "Usage: runit [-c] [-f procfile|Procfile] [-e envfile|.env]
-c: check procfile and envfile
-f: load the procfile
-e: load the envfile
-h: help information
"
}
#####################################
#Brief:
# verify the envfile
#Globals:
# none
#Agruments:
# envfile
#Returns:
# succ:0
# fail:1
#####################################
function verify_env() {
local env_file="$1"
local ret_val=0
[[ ! -f "${env_file}" ]] && my_err "verify envfile not exists" && return 1
while read nextline; do
if echo "${nextline}" | grep -v "="; then
my_err "no_equal_mark"
continue
fi
key="${nextline%%=*}"
value="${nextline#*=}"
echo "${key}" | grep -q "[^a-zA-Z_]" && my_err "invalid_char" && ret_val=1
echo "${value}" | grep -qE "[[:space:]]" && my_err "value_have_space" && ret_val=1
done < <(grep -vE "^[[:space:]]*#" "${env_file}" | grep -v "^$")
return ${ret_val}
}
#####################################
#Brief:
# verify the procfile
#Globals:
# none
#Agruments:
# procfile
#Returns:
# succ:0
# fail:1
#####################################
function verify_proc() {
local proc_file="$1"
local ret_val=0
[[ ! -f "${proc_file}" ]] && my_err "verify procfile not exists" && return 1
while read nextline ; do
if echo "${nextline}" | grep -v ":"; then
my_err "no_colon_command"
continue
fi
key="${nextline%%:*}"
value="${nextline#*:}"
echo "${key}" | grep -q [^a-zA-Z_] && my_err "invalid_char" && ret_val=1
done < <(grep -vE "^[[:space:]]*#" "${proc_file}")
return ${ret_val}
}
#####################################
#Brief:
# echo the error information
#Globals:
# none
#Agruments:
# errinfo
#Returns:
# none
#####################################
function my_err() {
errinfo="$1"
echo "${errinfo}"
}
#####################################
#Brief:
# echo the log information
#Globals:
# COLOR_ARRAY
#Agruments:
# proc_name command
#Returns:
# succ:0
# fail:1
#####################################
function log() {
local name="$1"
local command="$2"
local color="$3"
cur_time=$(date +%H:%M:%s)
printf "\E[${color}m${cur_time} %-7s] | " "${name}"
tput sgr0
echo "${command}"
return 0
}
#####################################
#Brief:
# execute the command
#Globals:
# COLOR_ARRAY PORT
#Agruments:
# proc_name command
#Returns:
# succ:0
# fail:1
#####################################
function run_command() {
local number="1"
local proc_name="$1"
local command="$2"
local cur_pid=$!
local cur_color="${COLOR_ARRAY[$number]}"
local comm_port=$(echo "${command}" | grep -e "\$PORT")
[[ -n "${comm_port}" ]] && [[ -z "${PORT}" ]] && PORT=8080
bash -c "${command}" > >(
while read result; do
log "${proc_name}" "${result}" "${COLOR}"
done
) 2>&1 &
local output="$(eval echo \"${command}\")"
log "${proc_name}" "${output} start with pid ${cur_pid}" "${cur_color}"
[[ $? -ne 0 ]] && return 1
[[ -n "${comm_port}" ]] && PORT=$((${PORT} + 1))
(( number ++ ))
return 0
}
#####################################
#Brief:
# load the env_file
#Globals:
# none
#Agruments:
# envfile
#Returns:
# succ:0
# fail:1
#####################################
function load_env_file() {
set -a
local env_lists="$1"
for flag in $(echo "${env_lists}"); do
[[ -f "${flag}" ]] && source "${flag}"
done
return 0
}
#####################################
#Brief:
# run procfile
#Globals:
# none
#Agruments:
# procfile
#Returns:
# succ:0
# fail:1
#####################################
function run_procfile() {
local proc_file="$1"
[[ ! -f "${proc_file}" ]] && my_err "this procfile is not exists" && return 1
while read nextline; do
if echo "${nextline}" | grep -qv ":"; then
my_err "no_colon_command"
continue
fi
local key="${nextline%%:*}"
local value="${nextline#*:}"
[[ -n "${key}" ]] && [[ -n "${value}" ]] && run_command "${key}" "${value}"
[[ $? -ne 0 ]] && return 1
done < <(grep "" "${proc_file}" | grep -vE "[[:space:]]*#" | grep -v "^$" )
wait
return 0
}
#####################################
#Brief:
# main procedure
#Globals:
# procfile encfile
#Agruments:
# none
#Returns:
# none
#####################################
function main() {
local check=false
while getopts "f:e:ch" flag
do
case ${flag} in
c) check=true ;;
f) procfile="${OPTARG}" ;;
e) envfile="${OPTARG}" ;;
*) usage ;;
esac
done
if ${check}; then
if [[ -n "${procfile}" ]]; then
verify_proc "${procfile}"
PROC_RET_VALUE=$?
[[ ${PROC_RET_VALUE} -ne 0 ]] && exit 1
else
my_err "The procfile is null"
exit 1
fi
if [[ -z "${envfile}" ]];then
envfile="./.env"
fi
verify_env "${envfile}"
ENV_RET_VALUE=$?
[[ ${ENV_RET_VALUE} -ne 0 ]] && exit 1
else
if [[ -z "${envfile}" ]]; then
envfile="./.env"
fi
load_env_file "${envfile}"
LOAD_ENV_RET_VALUE=$?
[[ ${LOAD_ENV_RET_VALUE} -ne 0 ]] && exit 1
if [[ -z "${procfile}" ]]; then
procfile="./Procfile"
fi
run_procfile "${procfile}"
RUN_RET_VALUE=$?
[[ RUN_RET_VALUE -ne 0 ]] && exit 1
fi
exit 0
}
main "$@"