Просмотр исходного кода

add shellcheck to ci (#4126)

* chore(shell): disable some shellcheck

* refactor(shell): fix shellcheck

* test(ci): add shellcheck
Yudai Kiyofuji 5 лет назад
Родитель
Сommit
aadd1ba11a

+ 47 - 46
.ci/build_packages/tests.sh

@@ -7,7 +7,7 @@ export RELUP_PACKAGE_PATH="/emqx/relup_packages/${EMQX_NAME}"
 # export EMQX_NODE_COOKIE=$(date +%s%N)
 
 emqx_prepare(){
-    mkdir -p ${PACKAGE_PATH}
+    mkdir -p "${PACKAGE_PATH}"
 
     if [ ! -d "/paho-mqtt-testing" ]; then
         git clone -b develop-4.0 https://github.com/emqx/paho.mqtt.testing.git /paho-mqtt-testing
@@ -16,28 +16,28 @@ emqx_prepare(){
 }
 
 emqx_test(){
-    cd ${PACKAGE_PATH}
+    cd "${PACKAGE_PATH}"
 
-    for var in $(ls $PACKAGE_PATH/${EMQX_NAME}-*);do
+    for var in "$PACKAGE_PATH"/"${EMQX_NAME}"-*;do
         case ${var##*.} in
             "zip")
-                packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.zip`
-                unzip -q ${PACKAGE_PATH}/$packagename
-                sed -i "/zone.external.server_keepalive/c zone.external.server_keepalive = 60" ${PACKAGE_PATH}/emqx/etc/emqx.conf
-                sed -i "/mqtt.max_topic_alias/c mqtt.max_topic_alias = 10" ${PACKAGE_PATH}/emqx/etc/emqx.conf
-                sed -i '/emqx_telemetry/d' ${PACKAGE_PATH}/emqx/data/loaded_plugins
-
-                if [ ! -z $(echo ${EMQX_DEPS_DEFAULT_VSN#v} | grep -oE "[0-9]+\.[0-9]+(\.[0-9]+)?-(alpha|beta|rc)\.[0-9]") ]; then
-                    if [ ! -d ${PACKAGE_PATH}/emqx/lib/emqx-${EMQX_DEPS_DEFAULT_VSN#v} ] || [ ! -d ${PACKAGE_PATH}/emqx/releases/${EMQX_DEPS_DEFAULT_VSN#v} ] ;then
+                packagename=$(basename "${PACKAGE_PATH}/${EMQX_NAME}"-*.zip)
+                unzip -q "${PACKAGE_PATH}/${packagename}"
+                sed -i "/zone.external.server_keepalive/c zone.external.server_keepalive = 60" "${PACKAGE_PATH}"/emqx/etc/emqx.conf
+                sed -i "/mqtt.max_topic_alias/c mqtt.max_topic_alias = 10" "${PACKAGE_PATH}"/emqx/etc/emqx.conf
+                sed -i '/emqx_telemetry/d' "${PACKAGE_PATH}"/emqx/data/loaded_plugins
+
+                if echo "${EMQX_DEPS_DEFAULT_VSN#v}" | grep -qE "[0-9]+\.[0-9]+(\.[0-9]+)?-(alpha|beta|rc)\.[0-9]"; then
+                    if [ ! -d "${PACKAGE_PATH}/emqx/lib/emqx-${EMQX_DEPS_DEFAULT_VSN#v}" ] || [ ! -d "${PACKAGE_PATH}/emqx/releases/${EMQX_DEPS_DEFAULT_VSN#v}" ] ;then
                         echo "emqx zip version error"
                         exit 1
                     fi
                 fi
 
                 echo "running ${packagename} start"
-                ${PACKAGE_PATH}/emqx/bin/emqx start || tail ${PACKAGE_PATH}/emqx/log/erlang.log.1
+                "${PACKAGE_PATH}"/emqx/bin/emqx start || tail "${PACKAGE_PATH}"/emqx/log/erlang.log.1
                 IDLE_TIME=0
-                while [ -z "$(${PACKAGE_PATH}/emqx/bin/emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
+                while [ -z "$("${PACKAGE_PATH}"/emqx/bin/emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
                 do
                     if [ $IDLE_TIME -gt 10 ]
                     then
@@ -48,54 +48,54 @@ emqx_test(){
                     IDLE_TIME=$((IDLE_TIME+1))
                 done
                 pytest -v /paho-mqtt-testing/interoperability/test_client/V5/test_connect.py::test_basic
-                ${PACKAGE_PATH}/emqx/bin/emqx stop
+                "${PACKAGE_PATH}"/emqx/bin/emqx stop
                 echo "running ${packagename} stop"
-                rm -rf ${PACKAGE_PATH}/emqx
+                rm -rf "${PACKAGE_PATH}"/emqx
             ;;
             "deb")
-                packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.deb`
-                dpkg -i ${PACKAGE_PATH}/$packagename
-                if [ $(dpkg -l |grep emqx |awk '{print $1}') != "ii" ]
+                packagename=$(basename "${PACKAGE_PATH}/${EMQX_NAME}"-*.deb)
+                dpkg -i "${PACKAGE_PATH}/${packagename}"
+                if [ "$(dpkg -l |grep emqx |awk '{print $1}')" != "ii" ]
                 then
                     echo "package install error"
                     exit 1
                 fi
-                
+
                 echo "running ${packagename} start"
-                running_test 
+                running_test
                 echo "running ${packagename} stop"
 
-                dpkg -r ${EMQX_NAME} 
-                if [ $(dpkg -l |grep emqx |awk '{print $1}') != "rc" ]
+                dpkg -r "${EMQX_NAME}"
+                if [ "$(dpkg -l |grep emqx |awk '{print $1}')" != "rc" ]
                 then
                     echo "package remove error"
                     exit 1
                 fi
 
-                dpkg -P ${EMQX_NAME}
-                if [ ! -z "$(dpkg -l |grep emqx)" ]
+                dpkg -P "${EMQX_NAME}"
+                if dpkg -l |grep -q emqx
                 then
                     echo "package uninstall error"
                     exit 1
                 fi
             ;;
             "rpm")
-                packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.rpm`
-                rpm -ivh ${PACKAGE_PATH}/$packagename
-                if [ -z $(rpm -q emqx | grep -o emqx) ];then
+                packagename=$(basename "${PACKAGE_PATH}/${EMQX_NAME}"-*.rpm)
+                rpm -ivh "${PACKAGE_PATH}/${packagename}"
+                if ! rpm -q emqx | grep -q emqx; then
                     echo "package install error"
                     exit 1
                 fi
-                
+
                 echo "running ${packagename} start"
-                running_test 
+                running_test
                 echo "running ${packagename} stop"
-                
-                rpm -e ${EMQX_NAME}
+
+                rpm -e "${EMQX_NAME}"
                 if [ "$(rpm -q emqx)" != "package emqx is not installed" ];then
                     echo "package uninstall error"
                     exit 1
-                fi  
+                fi
             ;;
 
         esac
@@ -103,8 +103,8 @@ emqx_test(){
 }
 
 running_test(){
-    if [ ! -z $(echo ${EMQX_DEPS_DEFAULT_VSN#v} | grep -oE "[0-9]+\.[0-9]+(\.[0-9]+)?-(alpha|beta|rc)\.[0-9]") ]; then
-        if [ ! -d /usr/lib/emqx/lib/emqx-${EMQX_DEPS_DEFAULT_VSN#v} ] || [ ! -d /usr/lib/emqx/releases/${EMQX_DEPS_DEFAULT_VSN#v} ];then
+    if echo "${EMQX_DEPS_DEFAULT_VSN#v}" | grep -qE "[0-9]+\.[0-9]+(\.[0-9]+)?-(alpha|beta|rc)\.[0-9]"; then
+        if [ ! -d /usr/lib/emqx/lib/emqx-"${EMQX_DEPS_DEFAULT_VSN#v}" ] || [ ! -d /usr/lib/emqx/releases/"${EMQX_DEPS_DEFAULT_VSN#v}" ];then
             echo "emqx package version error"
             exit 1
         fi
@@ -127,11 +127,12 @@ running_test(){
         IDLE_TIME=$((IDLE_TIME+1))
     done
     pytest -v /paho-mqtt-testing/interoperability/test_client/V5/test_connect.py::test_basic
-    emqx stop || kill $(ps -ef | grep -E '\-progname\s.+emqx\s' |awk '{print $2}')
+    # shellcheck disable=SC2009 # pgrep does not support Extended Regular Expressions
+    emqx stop || kill "$(ps -ef | grep -E '\-progname\s.+emqx\s' |awk '{print $2}')"
 
-    if [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = ubuntu ] \
-    || [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = debian ] \
-    || [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = raspbian ];then
+    if [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = ubuntu ] \
+    || [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = debian ] \
+    || [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = raspbian ];then
         service emqx start || tail /var/log/emqx/erlang.log.1
         IDLE_TIME=0
         while [ -z "$(emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
@@ -149,18 +150,18 @@ running_test(){
 }
 
 relup_test(){
-    if [ -d ${RELUP_PACKAGE_PATH} ];then
-        cd ${RELUP_PACKAGE_PATH }
+    if [ -d "${RELUP_PACKAGE_PATH}" ];then
+        cd "${RELUP_PACKAGE_PATH }"
 
-        for var in $(ls ${EMQX_NAME}-*-$(uname -m).zip);do
-            packagename=`basename ${var}`
-            unzip $packagename
+        for var in "${EMQX_NAME}"-*-"$(uname -m)".zip;do
+            packagename=$(basename "${var}")
+            unzip "$packagename"
             ./emqx/bin/emqx start
             ./emqx/bin/emqx_ctl status
             ./emqx/bin/emqx versions
-            cp ${PACKAGE_PATH}/${EMQX_NAME}-*-${EMQX_DEPS_DEFAULT_VSN#v}-$(uname -m).zip ./emqx/releases
-            ./emqx/bin/emqx install ${EMQX_DEPS_DEFAULT_VSN#v}
-            [ $(./emqx/bin/emqx versions |grep permanent | grep -oE "[0-9].[0-9].[0-9]") = ${EMQX_DEPS_DEFAULT_VSN#v} ] || exit 1
+            cp "${PACKAGE_PATH}/${EMQX_NAME}"-*-"${EMQX_DEPS_DEFAULT_VSN#v}-$(uname -m)".zip ./emqx/releases
+            ./emqx/bin/emqx install "${EMQX_DEPS_DEFAULT_VSN#v}"
+            [ "$(./emqx/bin/emqx versions |grep permanent | grep -oE "[0-9].[0-9].[0-9]")" = "${EMQX_DEPS_DEFAULT_VSN#v}" ] || exit 1
             ./emqx/bin/emqx_ctl status
             ./emqx/bin/emqx stop
             rm -rf emqx

+ 4 - 4
.ci/compatibility_tests/redis/redis.sh

@@ -31,7 +31,7 @@ rm -f \
     /data/conf/nodes.7001.conf \
     /data/conf/nodes.7002.conf ;
 
-if [ ${node} = "cluster" ] ; then
+if [ "${node}" = "cluster" ] ; then
   if $tls ; then
     redis-server /data/conf/redis-tls.conf --port 7000 --cluster-config-file /data/conf/nodes.7000.conf \
                                            --tls-port 8000 --cluster-enabled yes ;
@@ -44,7 +44,7 @@ if [ ${node} = "cluster" ] ; then
     redis-server /data/conf/redis.conf --port 7001 --cluster-config-file /data/conf/nodes.7001.conf --cluster-enabled yes;
     redis-server /data/conf/redis.conf --port 7002 --cluster-config-file /data/conf/nodes.7002.conf --cluster-enabled yes;
   fi
-elif [ ${node} = "sentinel" ] ; then
+elif [ "${node}" = "sentinel" ] ; then
     redis-server /data/conf/redis.conf --port 7000 --cluster-config-file /data/conf/nodes.7000.conf \
                                        --cluster-enabled no;
     redis-server /data/conf/redis.conf --port 7001 --cluster-config-file /data/conf/nodes.7001.conf \
@@ -75,9 +75,9 @@ do
     else
         continue;
     fi
-    if [ ${node} = "cluster" ] ; then
+    if [ "${node}" = "cluster" ] ; then
       yes "yes" | redis-cli --cluster create 172.16.239.10:7000 172.16.239.10:7001 172.16.239.10:7002;
-    elif [ ${node} = "sentinel" ] ; then
+    elif [ "${node}" = "sentinel" ] ; then
       cp /data/conf/sentinel.conf /_sentinel.conf
       redis-server /_sentinel.conf --sentinel;
     fi

+ 18 - 0
.github/workflows/shellcheck.yaml

@@ -0,0 +1,18 @@
+name: Shellcheck
+
+on: [pull_request]
+
+jobs:
+  shellcheck:
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Checkout source code
+        uses: actions/checkout@master
+      - name: Install shellcheck
+        run: |
+          sudo apt-get update
+          sudo apt install shellcheck
+      - name: Run shellcheck
+        run: |
+          ./scripts/shellcheck.sh
+          echo "success"

+ 54 - 46
bin/emqx

@@ -1,11 +1,12 @@
-#!/bin/sh
+#!/bin/bash
 # -*- tab-width:4;indent-tabs-mode:nil -*-
 # ex: ts=4 sw=4 et
 
 set -e
 
-ROOT_DIR="$(cd $(dirname $(readlink $0 || echo $0))/..; pwd -P)"
-. $ROOT_DIR/releases/emqx_vars
+ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
+# shellcheck disable=SC1090
+. "$ROOT_DIR"/releases/emqx_vars
 
 RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
 CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
@@ -79,7 +80,7 @@ relx_usage() {
 check_user() {
     # Validate that the user running the script is the owner of the
     # RUN_DIR.
-    if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then
+    if [ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]; then
         if [ "x$WHOAMI" != "xroot" ]; then
             echo "You need to be root or use sudo to run this command"
             exit 1
@@ -90,13 +91,13 @@ check_user() {
         done
         # This will drop priviledges into the runner user
         # It exec's in a new shell and the current shell will exit
-        exec su - $RUNNER_USER -c "$CMD"
+        exec su - "$RUNNER_USER" -c "$CMD"
     fi
 }
 
 
 # Make sure the user running this script is the owner and/or su to that user
-check_user $@
+check_user "$@"
 ES=$?
 if [ "$ES" -ne 0 ]; then
     exit $ES
@@ -109,7 +110,7 @@ else
 fi
 
 # Warn the user if ulimit -n is less than 1024
-ULIMIT_F=`ulimit -n`
+ULIMIT_F=$(ulimit -n)
 if [ "$ULIMIT_F" -lt 1024 ]; then
     echo "!!!!"
     echo "!!!! WARNING: ulimit -n is ${ULIMIT_F}; 1024 is the recommended minimum."
@@ -133,6 +134,7 @@ esac
 relx_get_pid() {
     if output="$(relx_nodetool rpcterms os getpid)"
     then
+        # shellcheck disable=SC2001 # Escaped quote taken as closing quote in editor
         echo "$output" | sed -e 's/"//g'
         return 0
     else
@@ -143,7 +145,7 @@ relx_get_pid() {
 
 relx_get_nodename() {
     id="longname$(relx_gen_id)-${NAME}"
-    "$BINDIR/erl" -boot "$REL_DIR/start_clean" -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell ${NAME_TYPE} $id
+    "$BINDIR/erl" -boot "$REL_DIR/start_clean" -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell "${NAME_TYPE}" "$id"
 }
 
 # Connect to a remote node
@@ -154,10 +156,11 @@ relx_rem_sh() {
     # Get the node's ticktime so that we use the same thing.
     TICKTIME="$(relx_nodetool rpcterms net_kernel get_net_ticktime)"
 
+    # shellcheck disable=SC2086 # $EPMD_ARG is supposed to be split by whitespace
     # Setup remote shell command to control node
     exec "$BINDIR/erl" "$NAME_TYPE" "$id" -remsh "$NAME" -boot "$REL_DIR/start_clean" \
          -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
-         -setcookie "$COOKIE" -hidden -kernel net_ticktime $TICKTIME $EPMD_ARG
+         -setcookie "$COOKIE" -hidden -kernel net_ticktime "$TICKTIME" $EPMD_ARG
 }
 
 # Generate a random id
@@ -171,7 +174,7 @@ relx_nodetool() {
 
     ERL_FLAGS="$ERL_FLAGS $EPMD_ARG" \
     "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \
-                                -setcookie "$COOKIE" "$command" $@
+                                -setcookie "$COOKIE" "$command" "$@"
 }
 
 # Run an escript in the node's environment
@@ -179,7 +182,7 @@ relx_escript() {
     shift; scriptpath="$1"; shift
     export RUNNER_ROOT_DIR
 
-    "$ERTS_DIR/bin/escript" "$ROOTDIR/$scriptpath" $@
+    "$ERTS_DIR/bin/escript" "$ROOTDIR/$scriptpath" "$@"
 }
 
 # Output a start command for the last argument of run_erl
@@ -202,19 +205,19 @@ generate_config() {
         # the vm, we need to pass it in twice.
         CONFIG_ARGS=" -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -vm_args $RUNNER_ETC_DIR/vm.args "
     else
-        CONFIG_ARGS=`$ERTS_PATH/escript $RUNNER_ROOT_DIR/bin/cuttlefish -i $REL_DIR/emqx.schema -c $RUNNER_ETC_DIR/emqx.conf -d $RUNNER_DATA_DIR/configs generate`
+        CONFIG_ARGS=$("$ERTS_PATH"/escript "$RUNNER_ROOT_DIR"/bin/cuttlefish -i "$REL_DIR"/emqx.schema -c "$RUNNER_ETC_DIR"/emqx.conf -d "$RUNNER_DATA_DIR"/configs generate)
 
         ## Merge cuttlefish generated *.args into the vm.args
-        CUTTLE_GEN_ARG_FILE=`echo "$CONFIG_ARGS" | sed -n 's/^.*\(vm_args[[:space:]]\)//p' | awk '{print $1}'`
+        CUTTLE_GEN_ARG_FILE=$(echo "$CONFIG_ARGS" | sed -n 's/^.*\(vm_args[[:space:]]\)//p' | awk '{print $1}')
         TMP_ARG_FILE="$RUNNER_DATA_DIR/configs/vm.args.tmp"
         cp "$RUNNER_ETC_DIR/vm.args" "$TMP_ARG_FILE"
         echo "" >> "$TMP_ARG_FILE"
-        sed '/^#/d' $CUTTLE_GEN_ARG_FILE | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
-            ARG_KEY=`echo "$ARG_LINE" | awk '{$NF="";print}'`
-            ARG_VALUE=`echo "$ARG_LINE" | awk '{print $NF}'`
-            TMP_ARG_VALUE=`grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $NF}'`
+        sed '/^#/d' "$CUTTLE_GEN_ARG_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
+            ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}')
+            ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}')
+            TMP_ARG_VALUE=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $NF}')
             if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then
-                if [ ! -z $TMP_ARG_VALUE ]; then
+                if [ -n "$TMP_ARG_VALUE" ]; then
                     sh -c "$SED_REPLACE 's/^$ARG_KEY.*$/$ARG_LINE/' $TMP_ARG_FILE"
                 else
                     echo "$ARG_LINE" >> "$TMP_ARG_FILE"
@@ -224,6 +227,7 @@ generate_config() {
         mv -f "$TMP_ARG_FILE" "$CUTTLE_GEN_ARG_FILE"
     fi
 
+    # shellcheck disable=SC2086
     if ! relx_nodetool chkconfig $CONFIG_ARGS; then
         echoerr "Error reading $CONFIG_ARGS"
         exit 1
@@ -233,7 +237,7 @@ generate_config() {
 # Call bootstrapd for daemon commands like start/stop/console
 bootstrapd() {
     if [ -e "$RUNNER_DATA_DIR/.erlang.cookie" ]; then
-        chown $RUNNER_USER $RUNNER_DATA_DIR/.erlang.cookie
+        chown "$RUNNER_USER" "$RUNNER_DATA_DIR"/.erlang.cookie
     fi
 }
 
@@ -249,8 +253,9 @@ fi
 if [ -z "$NAME_ARG" ]; then
     NODENAME="${EMQX_NODE_NAME:-}"
     # check if there is a node running, inspect its name
-    [ -z "$NODENAME" ] && NODENAME=`ps -ef | grep -E '\-progname\s.*emqx\s' | grep -o -E '\-name (\S*)' | awk '{print $2}'`
-    [ -z "$NODENAME" ] && NODENAME=`egrep '^[ \t]*node.name[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-`
+    # shellcheck disable=SC2009 # pgrep does not support Extended Regular Expressions
+    [ -z "$NODENAME" ] && NODENAME=$(ps -ef | grep -E '\-progname\s.*emqx\s' | grep -o -E '\-name (\S*)' | awk '{print $2}')
+    [ -z "$NODENAME" ] && NODENAME=$(grep -E '^[ \t]*node.name[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-)
     if [ -z "$NODENAME" ]; then
         echoerr "vm.args needs to have a -name parameter."
         echoerr "  -sname is not supported."
@@ -273,8 +278,9 @@ PIPE_DIR="${PIPE_DIR:-/$RUNNER_DATA_DIR/${WHOAMI}_erl_pipes/$NAME/}"
 if [ -z "$COOKIE_ARG" ]; then
     COOKIE="${EMQX_NODE_COOKIE:-}"
     # check if there is a node running, steal its cookie
-    [ -z "$COOKIE" ] && COOKIE=`ps -ef | grep -E '\-progname\s.*emqx\s' | grep -o -E '\-setcookie (\S*)' | awk '{print $2}'`
-    [ -z "$COOKIE" ] && COOKIE=`egrep '^[ \t]*node.cookie[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-`
+    # shellcheck disable=SC2009 # pgrep does not support Extended Regular Expressions
+    [ -z "$COOKIE" ] && COOKIE=$(ps -ef | grep -E '\-progname\s.*emqx\s' | grep -o -E '\-setcookie (\S*)' | awk '{print $2}')
+    [ -z "$COOKIE" ] && COOKIE=$(grep -E '^[ \t]*node.cookie[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-)
     if [ -z "$COOKIE" ]; then
         echoerr "vm.args needs to have a -setcookie parameter."
         echoerr "please check $RUNNER_ETC_DIR/emqx.conf"
@@ -288,7 +294,7 @@ fi
 COOKIE="$(echo "$COOKIE_ARG" | awk '{print $2}')"
 
 # Support for IPv6 Dist. See: https://github.com/emqtt/emqttd/issues/1460
-PROTO_DIST=`egrep '^[ \t]*cluster.proto_dist[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-`
+PROTO_DIST=$(grep -E '^[ \t]*cluster.proto_dist[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-)
 if [ -z "$PROTO_DIST" ]; then
     PROTO_DIST_ARG=""
 else
@@ -327,7 +333,7 @@ case "$1" in
             exit 1
         fi
         # Bootstrap daemon command (check perms & drop to $RUNNER_USER)
-        bootstrapd $@
+        bootstrapd
 
         # Save this for later.
         CMD=$1
@@ -343,7 +349,7 @@ case "$1" in
                 HEART_OPTION="start_boot"
                 ;;
         esac
-        RUN_PARAM="$@"
+        RUN_PARAM="$*"
 
         # Set arguments for the heart command
         set -- "$RUNNER_SCRIPT" "$HEART_OPTION"
@@ -366,9 +372,9 @@ case "$1" in
                           "$(relx_start_command)"
 
         WAIT_TIME=${WAIT_FOR_ERLANG:-15}
-        while [ $WAIT_TIME -gt 0 ]; do
+        while [ "$WAIT_TIME" -gt 0 ]; do
             if ! relx_nodetool "ping" >/dev/null 2>&1; then
-                WAIT_TIME=`expr $WAIT_TIME - 1`
+                WAIT_TIME=$((WAIT_TIME - 1))
                 sleep 1
                 continue
             fi
@@ -390,14 +396,14 @@ case "$1" in
         if ! relx_nodetool "stop"; then
             exit 1
         fi
-        while $(kill -s 0 "$PID" 2>/dev/null); do
+        while kill -s 0 "$PID" 2>/dev/null; do
             sleep 1
         done
         ;;
 
     restart|reboot)
-        echo "$EMQX_DISCR $REL_VSN is stopped: $($RUNNER_BIN_DIR/emqx stop)"
-        $RUNNER_BIN_DIR/emqx start
+        echo "$EMQX_DISCR $REL_VSN is stopped: $("$RUNNER_BIN_DIR"/emqx stop)"
+        "$RUNNER_BIN_DIR"/emqx start
         ;;
 
     pid)
@@ -416,7 +422,7 @@ case "$1" in
 
     escript)
         ## Run an escript under the node's environment
-        if ! relx_escript $@; then
+        if ! relx_escript "$@"; then
             exit 1
         fi
         ;;
@@ -429,7 +435,7 @@ case "$1" in
         fi
 
         # Bootstrap daemon command (check perms & drop to $RUNNER_USER)
-        bootstrapd $@
+        bootstrapd
 
         shift
         exec "$BINDIR/to_erl" "$PIPE_DIR"
@@ -443,7 +449,7 @@ case "$1" in
         fi
 
         # Bootstrap daemon command (check perms & drop to $RUNNER_USER)
-        bootstrapd $@
+        bootstrapd
 
         shift
         relx_rem_sh
@@ -485,7 +491,7 @@ case "$1" in
 
     console|console_clean|console_boot)
         # Bootstrap daemon command (check perms & drop to $RUNNER_USER)
-        bootstrapd $@
+        bootstrapd
 
         # .boot file typically just $REL_NAME (ie, the app name)
         # however, for debugging, sometimes start_clean.boot is useful.
@@ -519,8 +525,9 @@ case "$1" in
         export PROGNAME
 
         # Store passed arguments since they will be erased by `set`
-        ARGS="$@"
+        ARGS="$*"
 
+        # shellcheck disable=SC2086 # $RELX_CONFIG_PATH $CONFIG_ARGS $EPMD_ARG are supposed to be split by whitespace
         # Build an array of arguments to pass to exec later on
         # Build it here because this command will be used for logging.
         set -- "$BINDIR/erlexec" -boot "$BOOTFILE" -mode "$CODE_LOADING_MODE" \
@@ -529,12 +536,12 @@ case "$1" in
             $RELX_CONFIG_PATH $CONFIG_ARGS $EPMD_ARG
 
         # Dump environment info for logging purposes
-        echo "Exec: $@" -- ${1+$ARGS}
+        echo "Exec: $*" -- ${1+$ARGS}
         echo "Root: $ROOTDIR"
 
         # Log the startup
         echo "$RUNNER_ROOT_DIR"
-        logger -t "$REL_NAME[$$]" "Starting up"
+        logger -t "${REL_NAME[$$]}" "Starting up"
 
         # Start the VM
         exec "$@" -- ${1+$ARGS}
@@ -542,7 +549,7 @@ case "$1" in
 
     foreground)
         # Bootstrap daemon command (check perms & drop to $RUNNER_USER)
-        bootstrapd $@
+        bootstrapd
         # start up the release in the foreground for use by runit
         # or other supervision services
 
@@ -560,8 +567,9 @@ case "$1" in
         export PROGNAME
 
         # Store passed arguments since they will be erased by `set`
-        ARGS="$@"
+        ARGS="$*"
 
+        # shellcheck disable=SC2086 # $RELX_CONFIG_PATH $CONFIG_ARGS $EPMD_ARG are supposed to be split by whitespace
         # Build an array of arguments to pass to exec later on
         # Build it here because this command will be used for logging.
         set -- "$BINDIR/erlexec" $FOREGROUNDOPTIONS \
@@ -571,14 +579,14 @@ case "$1" in
             $RELX_CONFIG_PATH $CONFIG_ARGS $EPMD_ARG
 
         # Dump environment info for logging purposes
-        echo "Exec: $@" -- ${1+$ARGS}
+        echo "Exec: $*" -- ${1+$ARGS}
         echo "Root: $ROOTDIR"
 
         # Start the VM
         exec "$@" -- ${1+$ARGS}
         ;;
     ertspath)
-        echo $ERTS_PATH
+        echo "$ERTS_PATH"
         ;;
     rpc)
         # Make sure a node IS running
@@ -589,7 +597,7 @@ case "$1" in
 
         shift
 
-        relx_nodetool rpc $@
+        relx_nodetool rpc "$@"
         ;;
     rpcterms)
         # Make sure a node IS running
@@ -600,7 +608,7 @@ case "$1" in
 
         shift
 
-        relx_nodetool rpcterms $@
+        relx_nodetool rpcterms "$@"
         ;;
     root_dir)
         # Make sure a node IS running
@@ -620,10 +628,10 @@ case "$1" in
         fi
 
         shift
-        relx_nodetool "eval" $@
+        relx_nodetool "eval" "$@"
         ;;
     *)
-        relx_usage $1
+        relx_usage "$1"
         exit 1
         ;;
 esac

+ 11 - 8
bin/emqx_ctl

@@ -4,8 +4,9 @@
 
 set -e
 
-ROOT_DIR="$(cd $(dirname $(readlink $0 || echo $0))/..; pwd -P)"
-. $ROOT_DIR/releases/emqx_vars
+ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
+# shellcheck disable=SC1090
+. "$ROOT_DIR"/releases/emqx_vars
 
 # Echo to stderr on errors
 echoerr() { echo "$@" 1>&2; }
@@ -18,7 +19,7 @@ fi
 
 relx_get_nodename() {
     id="longname$(relx_gen_id)-${NAME}"
-    "$BINDIR/erl" -boot start_clean -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell ${NAME_TYPE} $id
+    "$BINDIR/erl" -boot start_clean -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell "${NAME_TYPE}" "$id"
 }
 
 # Control a node
@@ -34,8 +35,9 @@ relx_nodetool() {
 if [ -z "$NAME_ARG" ]; then
     NODENAME="${EMQX_NODE_NAME:-}"
     # check if there is a node running, inspect its name
-    [ -z "$NODENAME" ] && NODENAME=`ps -ef | grep -E '\progname\s.*emqx\s' | grep -o -E '\-name (\S*)' | awk '{print $2}'`
-    [ -z "$NODENAME" ] && NODENAME=`egrep '^[ \t]*node.name[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-`
+    # shellcheck disable=SC2009 # pgrep does not support Extended Regular Expressions
+    [ -z "$NODENAME" ] && NODENAME=$(ps -ef | grep -E '\progname\s.*emqx\s' | grep -o -E '\-name (\S*)' | awk '{print $2}')
+    [ -z "$NODENAME" ] && NODENAME=$(grep -E '^[ \t]*node.name[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-)
     if [ -z "$NODENAME" ]; then
         echoerr "vm.args needs to have a -name parameter."
         echoerr "  -sname is not supported."
@@ -54,8 +56,9 @@ NAME="$(echo "$NAME_ARG" | awk '{print $2}')"
 if [ -z "$COOKIE_ARG" ]; then
     COOKIE="${EMQX_NODE_COOKIE:-}"
     # check if there is a node running, steal its cookie
-    [ -z "$COOKIE" ] && COOKIE=`ps -ef | grep -E '\-progname\s.*emqx\s' | grep -o -E '\-setcookie (\S*)' | awk '{print $2}'`
-    [ -z "$COOKIE" ] && COOKIE=`egrep '^[ \t]*node.cookie[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-`
+    # shellcheck disable=SC2009 # pgrep does not support Extended Regular Expressions
+    [ -z "$COOKIE" ] && COOKIE=$(ps -ef | grep -E '\-progname\s.*emqx\s' | grep -o -E '\-setcookie (\S*)' | awk '{print $2}')
+    [ -z "$COOKIE" ] && COOKIE=$(grep -E '^[ \t]*node.cookie[ \t]*=[ \t]*' "$RUNNER_ETC_DIR/emqx.conf" 2> /dev/null | tail -1 | cut -d = -f 2-)
     if [ -z "$COOKIE" ]; then
         echoerr "vm.args needs to have a -setcookie parameter."
         echoerr "please check $RUNNER_ETC_DIR/emqx.conf"
@@ -69,7 +72,7 @@ fi
 COOKIE="$(echo "$COOKIE_ARG" | awk '{print $2}')"
 
 # Support for IPv6 Dist. See: https://github.com/emqtt/emqttd/issues/1460
-PROTO_DIST=`egrep '^[ \t]*cluster.proto_dist[ \t]*=[ \t]*' $RUNNER_ETC_DIR/emqx.conf 2> /dev/null | tail -1 | cut -d = -f 2-`
+PROTO_DIST=$(grep -E '^[ \t]*cluster.proto_dist[ \t]*=[ \t]*' "$RUNNER_ETC_DIR"/emqx.conf 2> /dev/null | tail -1 | cut -d = -f 2-)
 if [ -z "$PROTO_DIST" ]; then
     PROTO_DIST_ARG=""
 else

+ 2 - 1
build

@@ -71,7 +71,8 @@ make_relup() {
     local releases=()
     if [ -d "$releases_dir" ]; then
         while read -r dir; do
-            local version="$(basename "$dir")"
+            local version
+            version="$(basename "$dir")"
             # skip current version
             if [ "$version" != "$PKG_VSN" ]; then
                 releases+=( "$version" )

+ 6 - 6
deploy/docker/docker-entrypoint.sh

@@ -128,9 +128,9 @@ try_fill_config() {
     if grep -qE "^[#[:space:]]*$escaped_key\s*=" "$file"; then
         echo_value "$key" "$value"
         if [[ -z "$value" ]]; then
-            echo "$(sed -r "s/^[#[:space:]]*($escaped_key)\s*=\s*(.*)/# \1 = \2/" "$file")" > "$file"
+            sed -r "s/^[#[:space:]]*($escaped_key)\s*=\s*(.*)/# \1 = \2/" "$file" > tmpfile && cat tmpfile > "$file"
         else
-            echo "$(sed -r "s/^[#[:space:]]*($escaped_key)\s*=\s*(.*)/\1 = $escaped_value/" "$file")" > "$file"
+            sed -r "s/^[#[:space:]]*($escaped_key)\s*=\s*(.*)/\1 = $escaped_value/" "$file" > tmpfile && cat tmpfile > "$file"
         fi
     # Check if config has a numbering system, but no existing configuration line in file
     elif echo "$key" | grep -qE '\.\d+|\d+\.'; then
@@ -139,7 +139,7 @@ try_fill_config() {
             template="$(echo "$escaped_key" | sed -r -e 's/\\\.[0-9]+/\\.[0-9]+/g' -e 's/[0-9]+\\\./[0-9]+\\./g')"
             if grep -qE "^[#[:space:]]*$template\s*=" "$file"; then
                 echo_value "$key" "$value"
-                echo "$(sed '$a'\\ "$file")" > "$file"
+                sed '$a'\\ "$file" > tmpfile && cat tmpfile > "$file"
                 echo "$key = $value" >> "$file"
             fi
         fi
@@ -171,12 +171,12 @@ fill_tuples() {
     local elements=${*:2}
     for var in $elements; do
         if grep -qE "\{\s*$var\s*,\s*(true|false)\s*\}\s*\." "$file"; then
-            echo "$(sed -r "s/\{\s*($var)\s*,\s*(true|false)\s*\}\s*\./{\1, true}./1" "$file")" > "$file"
+            sed -r "s/\{\s*($var)\s*,\s*(true|false)\s*\}\s*\./{\1, true}./1" "$file" > tmpfile && mv tmpfile "$file"
         elif grep -q "$var\s*\." "$file"; then
             # backward compatible.
-            echo "$(sed -r "s/($var)\s*\./{\1, true}./1" "$file")" > "$file"
+            sed -r "s/($var)\s*\./{\1, true}./1" "$file" > tmpfile && cat tmpfile > "$file"
         else
-            echo "$(sed '$a'\\ "$file")" > "$file"
+            sed '$a'\\ "$file" > tmpfile && cat tmpfile > "$file"
             echo "{$var, true}." >> "$file"
         fi
     done

+ 10 - 7
deploy/packages/rpm/init.script

@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 #
 # emqx
 #
@@ -8,11 +8,11 @@
 #
 
 # Source function library.
+# shellcheck disable=SC1091
 . /etc/rc.d/init.d/functions
 
 RETVAL=0
 PATH=/sbin:/usr/sbin:/bin:/usr/bin
-DESC="EMQX, a distributed, massively scalable, highly extensible MQTT message broker written in Erlang/OTP"
 NAME=emqx
 DAEMON=/usr/bin/$NAME
 lockfile=/var/lock/subsys/$NAME
@@ -25,6 +25,7 @@ pidfile=/var/run/$NAME/$NAME.pid
 [ -d /var/lib/$NAME ] || exit 0
 
 # Read configuration variable file if it is present and readable
+# shellcheck disable=SC1090
 [ -r /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME
 
 # `service` strips all environmental VARS so
@@ -34,15 +35,17 @@ if [ -z "$HOME" ]; then
     export HOME=
 fi
 
-status -p $pidfile -l $(basename $lockfile) $NAME >/dev/null 2>&1
+status -p $pidfile -l "$(basename $lockfile)" $NAME >/dev/null 2>&1
 running=$?
 
 find_pid() {
+    # shellcheck disable=SC2009 # pgrep does not support Extended Regular Expressions
     ps ax | grep -E "\-progname\s+$NAME\s" | awk '{print $1}'
 }
 
 check_pid_status() {
-    local pid="$(find_pid)"
+    local pid
+    pid="$(find_pid)"
     if [ "$pid" = "" ]; then
         # prog not running?
         return 1
@@ -72,7 +75,7 @@ stop() {
     # Stop daemon.
     echo -n $"Shutting down emqx: "
     $DAEMON stop 2>/dev/null
-    for n in $(seq 1 10); do
+    for _ in $(seq 1 10); do
         sleep 1
         check_pid_status
         RETVAL=$?
@@ -93,7 +96,7 @@ stop() {
 hardstop() {
     echo -n $"Shutting down $NAME: "
     su - emqx -c "ps -ef | grep -E '\-progname\s+$NAME\s' | awk '{print \$2}' | xargs kill -9"
-    for n in $(seq 1 10); do
+    for _ in $(seq 1 10); do
         sleep 1
         check_pid_status
         RETVAL=$?
@@ -133,7 +136,7 @@ case "$1" in
         restart
         ;;
     status)
-        status -p $pidfile -l $(basename $lockfile) $NAME
+        status -p $pidfile -l "$(basename $lockfile)" $NAME
         ;;
     ping)
         $DAEMON ping || exit $?

+ 14 - 0
scripts/shellcheck.sh

@@ -0,0 +1,14 @@
+#!/bin/bash
+set -euo pipefail
+
+target_files=()
+while IFS='' read -r line; do target_files+=("$line"); done < <(grep -r -l --exclude-dir=.git --exclude-dir=_build "#!/bin/" .)
+return_code=0
+for i in "${target_files[@]}"; do
+  echo checking "$i" ...
+  if ! shellcheck "$i"; then
+    return_code=1
+  fi
+done
+
+exit $return_code

+ 4 - 4
scripts/start-two-nodes-in-docker.sh

@@ -39,8 +39,8 @@ docker run -d -t --restart=always --name "$NODE1" \
   -e EMQX_NODE_COOKIE="$COOKIE" \
   -e WAIT_FOR_ERLANG=60 \
   -p 18083:18083 \
-  -v $PROJ_DIR/_build/emqx/rel/emqx:/built \
-  $IMAGE sh -c 'cp -r /built /emqx && /emqx/bin/emqx console'
+  -v "$PROJ_DIR"/_build/emqx/rel/emqx:/built \
+  "$IMAGE" sh -c 'cp -r /built /emqx && /emqx/bin/emqx console'
 
 docker run -d -t --restart=always --name "$NODE2" \
   --net "$NET" \
@@ -48,8 +48,8 @@ docker run -d -t --restart=always --name "$NODE2" \
   -e EMQX_NODE_COOKIE="$COOKIE" \
   -e WAIT_FOR_ERLANG=60 \
   -p 18084:18083 \
-  -v $PROJ_DIR/_build/emqx/rel/emqx:/built \
-  $IMAGE sh -c 'cp -r /built /emqx && /emqx/bin/emqx console'
+  -v "$PROJ_DIR"/_build/emqx/rel/emqx:/built \
+  "$IMAGE" sh -c 'cp -r /built /emqx && /emqx/bin/emqx console'
 
 wait (){
   container="$1"

+ 12 - 8
sync-apps.sh

@@ -43,12 +43,14 @@ mkdir -p tmp/
 download_zip() {
     local app="$1"
     local ref="$2"
-    local vsn="$(echo "$ref" | tr '/' '-')"
+    local vsn
+    vsn="$(echo "$ref" | tr '/' '-')"
     local file="tmp/${app}-${vsn}.zip"
     if [ -f "$file" ] && [ "$force" != "force" ]; then
         return 0
     fi
-    local repo="$(echo "$app" | sed 's#_#-#g')"
+    local repo
+    repo=${app//_/-}
     local url="https://github.com/emqx/$repo/archive/$ref.zip"
     echo "downloading ${url}"
     curl -fLsS -o "$file" "$url"
@@ -56,7 +58,7 @@ download_zip() {
 
 default_vsn="dev/v4.3.0"
 download_zip "emqx_auth_mnesia" "e4.2.3"
-for app in ${apps[@]}; do
+for app in "${apps[@]}"; do
     download_zip "$app" "$default_vsn"
 done
 
@@ -64,7 +66,8 @@ extract_zip(){
     local app="$1"
     local ref="$2"
     local vsn_arg="${3:-}"
-    local vsn_dft="$(echo "$ref" | tr '/' '-')"
+    local vsn_dft
+    vsn_dft="$(echo "$ref" | tr '/' '-')"
     local vsn
     if [ -n "$vsn_arg" ]; then
         vsn="$vsn_arg"
@@ -72,14 +75,15 @@ extract_zip(){
         vsn="$vsn_dft"
     fi
     local file="tmp/${app}-${vsn_dft}.zip"
-    local repo="$(echo "$app" | sed 's#_#-#g')"
+    local repo
+    repo=${app//_/-}
     rm -rf "apps/${app}/"
     unzip "$file" -d apps/
     mv "apps/${repo}-${vsn}/" "apps/$app/"
 }
 
 extract_zip "emqx_auth_mnesia" "e4.2.3" "e4.2.3"
-for app in ${apps[@]}; do
+for app in "${apps[@]}"; do
     extract_zip "$app" "$default_vsn"
 done
 
@@ -95,6 +99,6 @@ cleanup_app(){
 }
 
 apps+=( "emqx_auth_mnesia" )
-for app in ${apps[@]}; do
-    cleanup_app $app
+for app in "${apps[@]}"; do
+    cleanup_app "$app"
 done