how to get bash multiple single line command

fedora132010

New Member
Credits
17
Hi Linux Gurus,

I'm trying to find how can i get the $? of the multiple script without using a function..

EXITSTATUS=0
cd directory/TODAY && command2 | command3
EXITSTATUS=`echo $?`

Scenario:

1. if i terminate the whole script --- it will show existstats=130
2. if i terminate command2 while the script is running --> $? return 0, even if i terminate it using ps -ef|grep command|awk '{print $2}"| kill -94
3. if i terminate command3 while the script is running --> $? return 0,even if i terminate it using ps -ef|grep command|awk '{print $2}"| kill -94

How can i get the status if any of the commands, if i terminate it using ps -ef|grep command|awk '{print $2}"| kill -94

I tried this :

cd directory/TODAY && command2 | command3 || EXITSTATUS=$?

however, it can only handle the command3 if it's terminated.
 


wizardfromoz

Super Moderator
Staff member
Gold Supporter
Credits
7,453
G'day @fedora132010 and welcome to linux.org.

Have a browse of the site before you post, and you'll find we have a Command Line subforum which handles scripting inquiries.

I'll move you there now.

Cheers

Chris Turner
wizardfromoz
 

JasKinasis

Well-Known Member
Credits
4,005
Use $PIPESTATUS.

$PIPESTATUS is an environment variable in bash that gets populated with an array of return values from the last operation that was executed.

If you execute a single command - it will only hold one value.
If you excute three piped commands - it will hold three return values.

In your use-case:
Bash:
command1 && command2 | command3
If command1 fails in the above chain - $PIPESTATUS will only hold the return code for command 1. The commands to the right of the && (AND) will not be ran.

If command1 succeeds - your piped commands command2 | command3 are executed and now $PIPESTATUS will hold the return codes for command2 and command3. Because they were the last piped commands that were ran.

command1 is a completely separate atomic operation, so the return code for that will have been overwritten by the return codes from the piped command2 | command3 operations.

Have a little play with this little script which uses $PIPESTATUS. Hopefully that will make things a little clearer:
Bash:
#!/usr/bin/env bash

# Create a variable holding a path to a directory in /tmp with todays date
# e.g. /tmp/2020-09-14
baseDir="/tmp/$(date +%Y-%m-%d)"

# If baseDir doesn't exist - create it
# Note: if you uncomment the next line - uncomment the cd/rmdir at line 19!
#[[ ! -d "${baseDir}" ]] && mkdir -p "${baseDir}"

# Attempt to CD into baseDir - if we succeed - run a command and pipe to another
# Note: Have a play with true and false at the end of this line:
cd "${baseDir}" &> /dev/null && true | false

# Get the return values of the last foreground command/s
retVals=(${PIPESTATUS[@]})

# Move back to original dir and remove basedir
#cd - &> /dev/null && rmdir "${baseDir}"

# Check our return values:
# If there are less than two retvals - we know the cd command failed
# So display an error message and exit this script with an error code
[[ ${#retVals[@]} -ne 2 ]] && echo "ERROR: Unable to cd into ${baseDir}" && exit 1

# Otherwise, we have two return values. Therefore our piped commands were issued
# Determine whether any of the commands exited with an error code:
[[ "${retVals[0]}" -ne "0" ]] && echo "command2 exited with an error: ${retVals[0]}"
[[ "${retVals[1]}" -ne "0" ]] && echo "command3 exited with an error: ${retVals[1]}"

# If one or more are showing errors - exit this script with an exit code
[[ "${retVals[0]}" -ne "0" ]] || [[ "${retVals[1]}" -ne "0" ]] && exit 1

# If we got here, everything executed successfully
echo "Everything ran with no errors!"
exit 0
It should be fairly self-explanatory, but for the sake of members of the community who are not programmers, or are new to scripting - I'll run through the script.

At the start of the script, we set up a variable called $baseDir which points to a sub-directory in /tmp/.
The name of the subdirectory is based on today's date.
e.g.
At the time of writing this, the directory would be called:
/tmp/2020-09-14

The script relies on the existence of this directory. It won't exist when we first run the script, but on subsequent runs, we'll uncomment a couple of lines of code in the script above.

Line 13 of our script is an important one :-
Bash:
cd "${baseDir}" &> /dev/null && true | false
This contains the commands that we're going to run and hopefully be able to see the return codes for, in $PIPESTATUS.

After the call to cd we are using an && (AND).
What this does is says - if the cd command completes successfully - i.e. returns 0 - then to execute the commands to the right of the && (AND).
In this particular case, we'll run the true command and pipe it's output to the false command.

NOTE: In bash, true and false are two shell builtins, where true represents success and false represents failure. true always returns 0 and false always returns 1.
In this script - we're using them to simulate the result of running some commands. So we'll be imagining that true is one command that has completed successfully and returned true and false is another command that has completed unsuccessfully and returned an error code.

The actual commands either side of the pipe could be anything - we're just being a little more abstract and hard coding some success/failure values into the script and seeing how the script handles them and the effects on $PIPESTATUS.


At line 16 we get ALL of the values from $PIPESTATUS in one go and store them in a variable called $retVals. We do this because the content of $PIPESTATUS is updated each time a command or a piped set of commands is ran.
That way our variable will store the return codes for the command/s that we're interested in and it doesn't matter if $PIPESTATUS is overwritten.

Lines 24 onwards look at the values we have in $retVal and decide what to do next.

So that's a high level overview of the script.
Save the script as whatever you like. Make it executable using:
Bash:
chmod +x nameofscript
Where nameofscript is whatever you called the script.

And run it using:
Bash:
./nameofscript
If you run the above script completely unmodified - with the commented-out lines still commented out - you should see the error message at line 24 ("Error unable to cd into .....") and the script will exit with a return code of 1 (failure).

The reason for this is because the required subdirectory in /tmp/ doesn't exist. So the cd command at line 13 will fail. Therefore, the piped commands to the right of the && at line 13 will not be ran and ${PIPESTATUS} and therefore $retVals only get populated with a single return code - The return code from the cd command, because that was the last command that ran.

Because we have the wrong number of return codes, we fail the check at line 24, which expects us to have two return codes.
Logically - it stands to reason, that if we don't have two return codes in $retVals, then the only command that ran was our cd command and it must have failed. So we display the "Error unable to cd into ....." message and our script exits with a return code of 1 (failure).

Now try uncommenting lines 9 and 19 and run the script again and you'll see the error message at line 29 ("command3 exited with an error: 1"

NOTE:
Line 9 ensures that our sub-directory in /tmp is created.
Line 19 moves us back to the previous working directory before cleaning up and removing the directory we added to /tmp/

So, what happened there?
Why are we seeing the error message at line 29?

The initial cd command at line 13 will succeed, because we've ensured that the directory will be there.
Therefore two piped commands at line 13, to the right of the && (AND) will be executed.

The first command is true, the output of which we pipe to the second command, false - so we're simulating that the first piped command succeeds and the second one fails.

After line 13 - $PIPESTATUS will be populated with the two returned values from the piped operation we just performed and they will be 0 and 1. We copy those values to our $retVals array at line 16.

Because there ARE two return values in $retVals, we will pass the check at line 24. So we know our piped commands were executed. So now we can take a look at their actual return values.

The first return value in $retVals is 0 (or true), so we will pass the test at line 28, no error message there.

The second return value in $retVals is 1 (or false), so we will FAIL the test at line 29 - and the script displays an error message about the failure of command3 and its return code.

Then finally at line 32 - the script will exit with a return value of 1 (error/failure).
Line 32 says if one or more of the values in retVal are not 0 (success) then we exit the script with a return value of 1 (failure).
Or in other words - if either of the piped commands failed - we exit with an exit code of 1 (failure)

Now if you try playing with true | false values at the end of line 13 and re-run the script you can see other side-effects.

If you set the end of line 13 to be false | false, you will see both of the error messages at lines 28 and 29 and the script bombs out with an error code at line 32.

If you set it to true | false, you'll see the error message at line 28 and the script bombs out with an error code at line 32.

If you set the end of line 13 to be true | true - indicating that command2 and command3 succeeded - you will see the message at line 35 and the script will exit with a return code of 0 (success).

So in the above - we've worked out how to use $PIPESTATUS in a script to monitor the success, or failure of several commands.



We can detect whether our cd command fails.
And we can see whether any of our piped commands have failed.
 

JasKinasis

Well-Known Member
Credits
4,005
So I have a record/layouts/apple and I need to place it in two better places and afterward eliminate the first. In this way,/layouts/apple will be duplicated to/formats/utilized AND/layouts/inuse and afterward after that I'd prefer to eliminate the first. Is cp the most ideal approach to do this, trailed by rm? Or on the other hand is there a superior way? I need to do it across the board line so I'm figuring it would look something like: cp/formats/apple/layouts/utilized | cp/formats/apple/formats/inuse | rm/layouts/apple Is this the right language structure?
snaptube vidmate word to pdf
First up, it's pretty bad form to hijack somebody else's thread with something completely off-topic and irrelevant. Please ask your question in your own thread!
Secondly - NO. Using pipes is NOT a good idea at all.
You would just use two cp commands and an rm command. No need for pipes whatsoever.
 


Members online


Top