10

I'm trying to get last modification date of a file with this command

TM_LOCAL=`ls -l --time-style=long-iso ~/.vimrc | awk '{ print $6" "$7 }'`

TM_LOCAL has value like "2012-05-16 23:18" after execution of this line

I'd also like to check PIPESTATUS to see if there was an error. For example if file does not exists, ls returns 2. But $? has value 0 as it has the return value of awk.

If I run this command alone, I can check the return value of ls by looking at ${PIPESTATUS[0]}

ls -l --time-style=long-iso ~/.vimrc | awk '{ print $6" "$7 }'

But $PIPESTATUS does not work as I've expected if I assign the output to a variable as in the first example. In this case, $PIPESTATUS array has only 1 element which is same as $?

So, the question is, how can I get both $PIPESTATUS and assign the output to a variable at the same time?

6 Answers 6

10

You could do this:

TM_LOCAL=$(ls -l --time-style=long-iso ~/.vimrc | \
             awk '{ print $6" "$7 }' ; exit ${PIPESTATUS[0]} )

Then $? will be the return code from ls. This doesn't work if you need the return code from more than one of the pipe parts (but you could split the pipeline if the output isn't too large, as it is here).

Here's a rather expensive way of getting the full PIPESTATUS array and the output. Not very elegant, but haven't found anything else:

result=$(echo -e "a\nb\nc" | \
          ( cat ; exit 1 ) | \
          ( cat ; exit 42 ) ; echo ${PIPESTATUS[@]})
output=$(head -n -1 <<< "$result")
status=($(tail -n 1 <<< "$result"))
echo "Output:"
echo "$output"
echo "Results:"
echo "${status[@]}"

Which gives:

Output:
a
b
c
Results:
0 1 42
2
  • This work in my case, but I'm still curious about if there is a way to get complete pipestatus array and the output. May 18, 2012 at 13:32
  • The head/tail parsing assumes the last line contains only statuses, which only works if previous output ends with a \n. If you add a guaranteed \n between them — echo; echo ${PIPESTATUS[@]}) — it should be possible to parse unambigously. I'm not certain but I think output=$(head -n -1 <<< "$result" | head -c -1) can work. But perhaps it's becoming cleaner to "smuggle" the statuses out by some other channel, e.g. temp file, or file descriptor 3... Dec 20, 2022 at 17:04
4

Use set -o pipefail in bash to get the right-most non-zero exit code in a piped command sequence as $?. From man bash:

If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands in the pipeline exit successfully. This option is disabled by default.

Then you can simply access $?. Use set +o pipefail to disable again.

3

The main problem with "what you expect" is that a command in backquotes is executed in a subshell; $PIPESTATUS exists there and the status returned fromm it follows the same rules as if you executed a single executable (or shell script). The status of the backquote command is the rightmost (awk) status.

To implement what @Daniel Beck said, set the pipefail option in the subshell thusly:

TM_LOCAL=`set -o pipefail; ls -l --time-style=long-iso ~/.vimrc | awk '{ print $6" "$7 }'` now the status stored in $? afterwards will be the status of ls (if non-zero).

However, I think an explicit if [ -f ~/.vimrc ];... test would be more readable.

You can't get output into a variable and PIPESTATUS returned without either a temporary file for the former, or marshalling the latter into a string.

2

I assume the issue here is that PIPESTATUS goes away in its entirety as soon as you execute a command. You can get the complete PIPESTATUS array in bash version 2 or above this way:

declare -a status
status=(${PIPESTATUS[@]})

Then access ${status[0]}, ${status[1]}, etc.

0

I wanted to send email fron cron only if the exit status was not zero

The trick is that to get the stdin for the end of the pipeline you need to to put it in a subshell - but that seems to hide the PIPESTATUS value ...

test cron spits out some output and exits with 1 or 0 ..

./testcron | (test ${PIPESTATUS[0]} -ne 0 || mail -s "testcron output" paul)

UPDATE: the PIPESTATUS is not visible until the pipeline command is processes

0

One option is to check for the existence of your file before getting its modification time with a call to stat. Since stat returns slightly more than you want in the timestamp, you can trim it using parameter expansion.

With GNU stat (e.g. on Linux), you could run:

[[ -f ~/.vimrc ]] && TM_LOCAL=$(stat -c '%y' ~/.vimrc 2>/dev/null)
TM_LOCAL=${TM_LOCAL%:*}  # Safe to do, even if stat fails

On Mac OS X and other BSD systems, stat syntax differs and can specify a time format:

[[ -f ~/.vimrc ]] && TM_LOCAL=$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' ~/.vimrc 2>/dev/null)
1
  • In what is now the GNU answer, you say the change to $TM_LOCAL is safe. It is only safe if you expected it not to have a prior value. Say the value was previously 2020-02-27 17:14 and there is no ~/.vimrc file. You'd then have 2020-02-27 17. I'd therefore chain those two lines together with an additional && or (preferably since that's not so legible) use an if stanza.
    – Adam Katz
    Feb 28, 2020 at 15:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .