Nicholas Adamou
Nicholas Adamou

Reputation: 741

How do I get the exit code of a PID outside of the parent shell?

Background

I am trying to get the exit code of a command launched via x-terminal-emulator -e. Using this command, it simply opens a new terminal window and passes it sh -c with the command that you want to be executed within the new terminal window. I then use a combination of ps ax, grep, xargs & cut to get the PID of the running process.

👨🏼‍💻tester.sh

#!/bin/bash

execute() {

    local -r CMDS="$1"
    local exitCode=0
    local cmdsPID=""

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Execute a command within a 
    # a new 'x-terminal-emulator' window.

    x-terminal-emulator -e "$CMDS" &> /dev/null

    # Get the PID of the process spawned by
    # 'x-terminal-emulator'.

    cmdsPID="$(ps ax | grep "sh -c $CMDS" | xargs | cut -d ' ' -f 1)"

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Wait for the commands to no longer be executing
    # in the background, and then get their exit code.

    wait "$cmdsPID" &> /dev/null
    exitCode=$?

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Print output based on what happened.

    echo "$exitCode"    

}

execute "apt update && apt upgrade -y && apt full-upgrade -y && apt autoremove -y && apt clean"

⚠️ Error

However, when I utilize the command wait to wait for the PID to finish running so that I can get the exit code of the command, wait returns:

pid #### is not a child of this shell

Is there a way where I can both wait for the PID to finish, then grab its exit code from a process not spawned under the parent shell?

Upvotes: 0

Views: 1284

Answers (3)

Nicholas Adamou
Nicholas Adamou

Reputation: 741

After a bit of trial and error and some research, I have come up with a solution.

My solution involves the following:

  1. The use of redirecting $? output from within the new terminal window that was spawned from x-terminal-emulator to a temporary file utilizing mktemp.
  2. Then, utilizing the until conditional structure to wait until that temporary file is not empty.
  3. Once the temporary file is not empty, I can then cat the contents of the file and store it within a variable. Then, echo the results.

👨🏼‍💻tester.sh (Refactored)

#!/bin/bash

execute() {

    local -r CMDS="$1"

    local -r EXIT_STATUS_FILE="$(mktemp /tmp/XXXXX)"

    local exitCode=0
    local cmdsPID=""

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Execute a command within a 
    # a new 'x-terminal-emulator' window.

    x-terminal-emulator -e "($CMDS) ; echo \$? > $EXIT_STATUS_FILE" \
        &> /dev/null

    # Get the PID of the process spawned by
    # 'x-terminal-emulator'.

    cmdsPID="$(ps ax | grep -v "grep" | grep -v "S+" | grep "sh -c" | grep "$CMDS" | xargs | cut -d ' ' -f 1)"

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Wait for the commands to no longer be executing
    # in the background, and then get their exit code.

    until [ -s "$EXIT_STATUS_FILE" ];
    do
        sleep 1
    done

    exitCode="$(cat "$EXIT_STATUS_FILE")"

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Print output based on what happened.

    echo "$exitCode"

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Remove temporary file.

    rm -rf "$EXIT_STATUS_FILE"  

}

execute \
  "apt update && apt upgrade -y && apt full-upgrade -y && apt autoremove -y && apt clean"

Upvotes: 1

Bayou
Bayou

Reputation: 3441

Why would you use another terminal and not create a child process? It's much more cleaner IMHO. This way you're able to use the wait command:

function i_exit {
        ($1) &
        wait $!
        return $?
}

i_exit "true"
echo $?
i_exit "false"
echo $?
i_exit "true && true"
echo $?
i_exit "true && false"
echo $?

You create a background process using "&". The PID created is stored in $! and when the child exits, you can return the value with $?. This is catchable out of the function with the same variable. The code above outputs:

$ ./test.sh 
0
1
0
0

No ps aux | grep whatever needed!

EDIT: Of course you can execute things in a terminal if you really wanted to. The following script does just that. I dont have xfce, but you can replace "xterm" with "x-terminal-emulator".

#! /bin/bash

function i_exit {
        (xterm -e "$1") &
        wait $!
        return $?
}

# Waits 5 seconds
i_exit "true && echo 'Test 1' && sleep 5s"
echo $?
# Exits immediately
i_exit "false && echo 'Test 2' && sleep 5s"
echo $?

Upvotes: 1

NumZ
NumZ

Reputation: 69

Is there a reason to launch your command in another xterm? your script seems to be sequential, so perhaps this solution could be acceptable?

#!/bin/bash

execute() {

    local -r CMDS="$1"
    local exitCode=0
    local cmdsPID=""

    output=`$CMDS > /dev/null`
    exitCode=$?
    echo $exitCode    
}
execute "apt update && apt upgrade -y && apt full-upgrade -y && apt autoremove -y && apt clean"

Upvotes: 1

Related Questions