bash -c "exec -a myProcessName ./script.sh &" 不会将该脚本发送到后台

bash -c "exec -a myProcessName ./script.sh &" 不会将该脚本发送到后台

从 bash 脚本中,我需要启动三个进程并为每个进程命名,以便能够在稍后的停止命令中停止它们。

# Démarrer les applications eco emploi : backend métier, backend du front (Java), et front (Angular)
demarrer_applications() {
  emit "application" "start" "init" "Démarrage des applications ecoemploi" "Soumission des scripts de démarrage"

  # Partie métier, backend Java
  log "INFO" "Demande de démarrage de l'application métier ecoemploi (Backend Java)"
  START_METIER=$(bash -c "exec -a $ECOEMPLOI_METIER_PROCESS_NAME ./submit_ecoemploi_metier_back.sh &")

  if [ $? -ne 0 ]; then
     emit "application" "start" "fail" "Soumission de ecoemploi backend métier échouée" "$START_METIER"
     log "ERROR" "La demande de soumision de l'application métier ecoemploi (Backend Java) a échoué : $START_METIER"
     exit $?
  fi

  # Partie front, backend Java
  log "INFO" "Demande de démarrage de l'ihm ecoemploi (Backend Java)"
  START_IHM_BACK=$(bash -c "exec -a $ECOEMPLOI_IHM_BACK_PROCESS_NAME ./submit_ecoemploi_ihm_back.sh &")

  if [ $? -ne 0 ]; then
     emit "application" "start" "fail" "Soumission de ecoemploi backend ihm échouée" "$START_IHM_BACK"
     log "ERROR" "La demande de soumision de l'application ihm ecoemploi (Backend Java) a échoué : $START_IHM_BACK"
     exit $?
  fi

  log "INFO" "Demande de démarrage de l'ihm ecoemploi (Front Angular)"
  START_IHM_FRONT=$(bash -c "exec -a $ECOEMPLOI_IHM_FRONT_PROCESS_NAME ./submit_ecoemploi_ihm_front.sh &")

  if [ $? -ne 0 ]; then
     emit "application" "start" "fail" "Soumission de ecoemploi ihm front échouée" "$START_IHM_FRONT"
     log "ERROR" "La demande de soumision de l'application ihm ecoemploi (Front Angular) a échoué : $START_IHM_FRONT"
     exit $?
  fi

  log "INFO" "Les demandes de soumission des applications ont été faites."
}

总结该脚本,它尝试运行以下命令:

bash -c "exec -a $ECOEMPLOI_METIER_PROCESS_NAME ./submit_ecoemploi_metier_back.sh &"
bash -c "exec -a $ECOEMPLOI_IHM_BACK_PROCESS_NAME ./submit_ecoemploi_ihm_back.sh &"
bash -c "exec -a $ECOEMPLOI_IHM_BACK_PROCESS_NAME ./submit_ecoemploi_ihm_back.sh &"

其中每个提交子脚本都具有以下形式:

#!/bin/bash
source ecoemploi-start-stop-common.sh

cd "$ECOEMPLOI_METIER_HOME"

if [ $? -ne 0 ]; then
   emit "application" "start" "fail" "Le répertoire d'installation de l'application métier, $ECOEMPLOI_METIER_HOME, est absent" ""
   log "ERROR" "Le répertoire d'installation de l'application métier, $ECOEMPLOI_METIER_HOME, est absent"
   exit $?
fi

./start.sh

例如,这里start.sh将启动一个 Java 17 应用程序:

java --add-exports java.base/sun.nio.ch=ALL-UNNAMED \
   --add-opens java.base/java.util=ALL-UNNAMED \
   --add-opens java.base/java.nio=ALL-UNNAMED \
   --add-opens java.base/java.lang=ALL-UNNAMED \
   --add-opens java.base/java.lang.invoke=ALL-UNNAMED \
   -jar target/application-metier-et-gestion.jar

此处ecoemploi-start-stop-common.sh仅定义几个目录位置和两个自定义函数:emit向 Kafka 主题发送消息,以及log登录控制台:

#!/bin/bash
#
# Répertoires d'installation de l'application, et noms des processus qui vont être lancés par bash/exec
ECOEMPLOI_HOME="/home/lebihan/dev/Java/comptes-france"
ECOEMPLOI_METIER_HOME="$ECOEMPLOI_HOME/metier-et-gestion/ApplicationMetierEtGestion"
ECOEMPLOI_IHM_FRONT_HOME="$ECOEMPLOI_HOME/web-client/ApplicationEtude/etude"
ECOEMPLOI_IHM_BACK_HOME="$ECOEMPLOI_HOME/web-client/ApplicationEtude"

ECOEMPLOI_METIER_PROCESS_NAME="ecoemploi_backend_metier"
ECOEMPLOI_IHM_BACK_PROCESS_NAME="ecoemploi_ihm_backend"
ECOEMPLOI_IHM_FRONT_PROCESS_NAME="ecoemploi_ihm_front"

# Communication avec Kafka
KAFKA_BOOTSTRAP_SERVER_PORT=9092
BOOTSTRAP_SERVER="localhost:$KAFKA_BOOTSTRAP_SERVER_PORT"
TOPIC_VIE="ecoemploi-statut"

# Logger un message, horodaté
# $1 Sevérité
# $2 Message
log() {
   TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")

   if [ "$1" = "ERROR" ]; then
      echo "$TIMESTAMP [$1] $2" >&2
   else
      echo "$TIMESTAMP [$1] $2"
   fi
}

# Emettre un évènement dans le $TOPIC_VIE de Kafka
# $1 : Composant
# $2 : Phase : init, start
# $3 : Statut : start, fail
# $4 : description
# $5 : Message
emit() {
# Laisser la ligne ci-dessous non indentée pour que EOF fonctionne.
MESSAGE_EMIT_KAFKA=$(cat << EOF
{
   "composant": "$1",
   "phase": "$2",
   "statut": "$3",
   "description": "$4",
   "message": "$5"
}
EOF
)

  MESSAGE_EMISSION_VERS_KAFKA=$(echo "$MESSAGE_EMIT_KAFKA" | kafka-console-producer.sh --broker-list $BOOTSTRAP_SERVER --topic $TOPIC_VIE)

  if [ $? -ne 0 ]; then
    log "WARN" "Le message vers le topic $TOPIC_VIE n'a pu être émis : $MESSAGE_EMISSION_VERS_KAFKA"
  fi
}

我的问题是,在我的 bash 函数中demarrer_applications(),在其他几个函数之后调用并生成最后一个 [INFO] 日志,

2023-04-14 10:01:09 [INFO] Démarrage de Zookeeper puis de Kafka...
2023-04-14 10:01:09 [INFO] Zookeeper et Kafka ont démarré.
2023-04-14 10:01:10 [INFO] Le topic ecoemploi-statut existant sera réutilisé sur Kafka localhost:9092.
2023-04-14 10:01:11 [INFO] Postgresql n'est pas démarré. Démarrage en cours...
2023-04-14 10:01:13 [INFO] Postgresql est prêt.
2023-04-14 10:01:14 [INFO] Demande de démarrage de l'application métier ecoemploi (Backend Java)

使用以下命令运行所需的应用程序:

bash -c "exec -a $ECOEMPLOI_METIER_PROCESS_NAME ./submit_ecoemploi_metier_back.sh &"

&之后./submit_ecoemploi_metier_back.sh不会将该应用程序发送到后台。我的主脚本无法继续其工作并启动接下来的应用程序。它挂在这里,等待 Ctrl-C。

我该如何submit_ecoemploi_*.sh在后台使用bash命令提交脚本exec?多谢!


@StéphaneChazelas

我使用你的解决方案来调整我的功能(我只将更改的行放在这里):

demarrer_applications() {
  # Partie métier, backend Java
  NAME=$ECOEMPLOI_METIER_PROCESS_NAME bash -c 'exec -a "$NAME" ./submit_ecoemploi_metier_back.sh &'

  NAME=$ECOEMPLOI_IHM_BACK_PROCESS_NAME bash -c 'exec -a "$NAME" ./submit_ecoemploi_ihm_back.sh &'

  NAME=$ECOEMPLOI_IHM_FRONT_PROCESS_NAME bash -c 'exec -a "$NAME" ./submit_ecoemploi_ihm_front.sh &'
}

它提交完美,我的应用程序看到它的三个组件已启动并且可用。

但 aps -f返回给我:

UID          PID    PPID  C STIME TTY          TIME CMD
lebihan   309213  309207  0 11:18 pts/0    00:00:00 bash
lebihan   322835    2710 21 11:26 pts/0    00:00:04 java -Xmx1G -Xms1G -server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -XX:Ma
lebihan   324800    2710  0 11:27 pts/0    00:00:00 /bin/bash /home/lebihan/dev/Java/comptes-france/submit_ecoemploi_metier_back.sh
lebihan   324803    2710  0 11:27 pts/0    00:00:00 /bin/bash /home/lebihan/dev/Java/comptes-france/submit_ecoemploi_ihm_back.sh
lebihan   324804  324800  0 11:27 pts/0    00:00:00 /bin/bash ./start.sh
lebihan   324806  324803  0 11:27 pts/0    00:00:00 /bin/bash ./start.sh
lebihan   324808  324804 99 11:27 pts/0    00:00:47 java --add-exports java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --
lebihan   324809    2710  0 11:27 pts/0    00:00:00 /bin/bash /home/lebihan/dev/Java/comptes-france/submit_ecoemploi_ihm_front.sh
lebihan   324811  324806 52 11:27 pts/0    00:00:07 java --add-exports java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --
lebihan   324812  324809  0 11:27 pts/0    00:00:00 /bin/bash ./start.sh
lebihan   324814  324812 51 11:27 pts/0    00:00:07 ng serve
lebihan   325316  309213  0 11:27 pts/0    00:00:00 ps -f

我没有看到进程名称发生变化。
那么 stop 命令无法停止submit_ecoemploi_ihm_front.sh, submit_ecoemploi_ihm_back.sh,submit_ecoemploi_metier_back.sh

答案1

output=$(cmd &)

cmd &通过管道收集运行代码的子 shell 的输出;在该子 shell 中启动的所有进程都会继承该管道作为其标准输出,无论它们是否异步启动。

shell将从该管道的另一端读取以进行扩展,直到到达该管道上的文件结尾,只有当在管道的写入端打开的所有文件描述符(由任何进程)都具有时才会发生这种情况已关闭,通常是在所有进程都已终止时。

无论如何,如果您在这些进程输出之前返回,则无法捕获这些进程的输出。

如果不需要其余的输出,您可以关闭异步启动的进程中的 stdout,以释放该管道上打开的 fd。

例如:

output=$(sh -c 'echo some output; sleep 1; exec>&-; sleep 120' &)

将在 1 秒后返回,而不是 121,在 stdout 关闭后离开sh并在后台运行。sleep 120

exec>&-比使用exec>/dev/null它也会关闭管道的 fd更好,但避免让 stdout 关闭,这通常是一个坏主意,因为应用程序通常期望它被打开。

如果您想捕获同时运行的多个命令的输出,则无法通过变量和命令替换来实现。在不支持select/poll类似功能的 bash 中,最好是使用临时文件:

exec -a name1 cmd1 > "$temp1" &
exec -a name2 cmd2 > "$temp2" &
exec -a name3 cmd3 > "$temp3" &
wait
output1=$(<"$temp1")
output2=$(<"$temp2")
output3=$(<"$temp3")

(假设是bash)。

这里不需要那些bash -c ...,顺便说一句:

bash -c "exec -a $ECOEMPLOI_METIER_PROCESS_NAME ./submit_ecoemploi_metier_back.sh &"

是错误的,因为它的内容ECOEMPLOI_METIER_PROCESS_NAME最终被解释为 bash 代码,这不是你想要的。您想要 is 被视为 的参数-a,所以它应该是:

NAME=$ECOEMPLOI_METIER_PROCESS_NAME bash -c '
  exec -a "$NAME" ./submit_ecoemploi_metier_back.sh &'

或者可能:

bash -c '"$@" &' bash exec -a "$ECOEMPLOI_METIER_PROCESS_NAME" ./submit_ecoemploi_metier_back.sh

bash -c但如果这是一个 bash 脚本,那么这些都是完全多余的。做就是了:

exec -a "$ECOEMPLOI_METIER_PROCESS_NAME" ./submit_ecoemploi_metier_back.sh &

无论如何,如果您script.sh 使用#! /bin/bashshebang 运行 a (因此.sh后缀会产生误导,使用.bash或不使用后缀) as exec -a name ./script.sh arg,shell 将会执行 a execve("./script.sh", ["name", "arg"], env),但由于这是一个脚本,至少在 Linux 上,它将依次运行execve("/bin/bash", ["/bin/bash", "./script.sh", "arg"], env),并且name会迷路。

您可以使用:

exec -a name bash ./script.sh

在这种情况下ps -f会显示name ./script.sh.请注意,它bash可以根据argv[0]收到的信息调整其行为。例如,如果使用argv[0]ofsh或以 结尾进行调用/sh,它将启用其 POSIX 模式。

或者:

BASH_ENV=./script.sh exec -a name bash -c ''

在这种情况下ps -f将显示"name -c ",但请注意,它将BASH_ENV最终出现在环境中,并且在任何时候启动另一个 bash 脚本(包括这些脚本)script.sh时都将获取mean,这可能会导致无限递归。bash -c inlinescript.sh

答案2

你所描述的大部分有效为我。不过我必须改变一小部分。这是我的工作示例:

一、内容script.sh

#!/bin/bash
while [ -f /tmp/script.run ]       # Run only while this file exists
do
    date >/tmp/script.date 2>&1    # Update the output file
    sleep 0.5                      # Pause briefly
done
rm -f /tmp/script.date

现在我们将按照您建议的方式运行它,

touch /tmp/script.run
bash -c "exec -a myProcessName ./script.sh & "

让我们看一下进程树ps -f

UID        PID  PPID  C STIME TTY          TIME CMD
roaima    9301  9296  0 09:41 pts/0    00:00:00 -bash
roaima    9913     1  0 09:46 pts/0    00:00:00 /bin/bash /home/roaima/script.sh
roaima    9919  9913  0 09:46 pts/0    00:00:00 sleep 0.5
roaima    9920  9301  0 09:46 pts/0    00:00:00 ps -f

您可以看到该进程正在运行(PID 9913 和 9919),但进程名称尚未应用。那是因为它是一个脚本,并且该脚本是由其行上列出的进程执行的#!

让我们停止该过程,稍等片刻,然后用不同的方法重试

rm -f /tmp/script.run    # Remove the flag file
ps -f                    # Confirm the process has ended

touch /tmp/script.run
bash -c "exec -a myProcessName bash ./script.sh & "

这次显式调用bash已按预期重命名:

UID        PID  PPID  C STIME TTY          TIME CMD
roaima    9301  9296  0 09:41 pts/0    00:00:00 -bash
roaima   11745     1  2 09:53 pts/0    00:00:00 myProcessName ./script.sh
roaima   11777 11745  0 09:53 pts/0    00:00:00 sleep 0.5
roaima   11778  9301  0 09:53 pts/0    00:00:00 ps -f

但实际上你也可以避免外部调用bash,直接调用exec

exec -a myProcessName bash ./script.sh &

最后,我建议跟踪进程的更好方法不是通过进程名称,而是通过 PID:

./script.sh &
scriptPID=$!

kill -0 $scriptPID && echo "The script is still running with PID $scriptPID"

删除标志文件/tmp/script.run,暂停片刻,然后重复该kill … && echo …命令。您将没有输出,因为脚本不再运行。

答案3

类似于 roaima 所写的关于这个著名地点的内容:

  # Initialize a file for collecting background process numbers:
    sync_file=/tmp/syncfile.123
    printf ""  > $sync_file
    # Repeat this block for all your parallel commands:
    # This can be a loop or a fixed number of process submissions
       # Run your command(s) in a sub-shell:
       (
        # Name your process using a single word:
        pname=MyProcess.1
        # Save the name and PID of the bg process 
        printf "$pname:$?\n" >> $sync_file
        # run your command(s)
        my_command
       ) &
    # End block
    
    # This part can be run in a separate script:
    # Kill your processes using PID list you have stored in the file
    sync_file=/tmp/syncfile.123
    for a in $( cat $sync_file )
    do
        procname=${a%:*}  # Get the process name 
        pid=${a#*:}       # Get the PID
        echo "Process: $procname PID: $pid"
        kill -9 $pid
    done

相关内容