1

How do I redirect stdout and/or stderr to a path I have specified in a variable? Note: I don't want to overwrite the variable itself, I want to make std[xxx] write to the file specified in the variable.

For example- a simple script that, if the scp command fails, instead of printing the failure message to stderr or stdout (I'm not sure to which it outputs when fail), it outputs it to a specified log file. The path to the log file is stored in the $LOG variable:

LOG=/path/to/file.log
scp file1 host@remote

# do 'whatever' if scp command succeeds:
if [ $? = 0 ];
then
    whatever
else
    # else log both stdout/stderr to ${LOG} file
    &>"${LOG}"
    # DEBUG - print contents of ${LOG} var for testing purposes
    printf "${LOG}"

The result of this script doesn't show anything in the /path/to/file.log file and simple prints /path/to/file.log to stdout. So it's as if nothing was written from &>.

I've already confirmed my particular scp command works, so I know that's not a potential issue.

Or is this even the most proper way to handle custom log files? Is there a better practice for configuring your own logging system than storing paths to log files in variables?

  • 3
    The redirection would have to happen before scp is run, or with the scp line, not after the scp has already gone away. – thrig Nov 18 '17 at 15:05
  • Why not unconditionally write to a temporary file and then test for success/failure? If failure, add the output to your permanent log file or print and discard it. – JRFerguson Nov 18 '17 at 15:13
0

It looks like you're trying to log the output after you run the command, which isn't possible.

If you want to log the output of the scp command unconditionally, then you just have to include the redirection operator on the same line as the command itself, i.e.:

&>"${LOG}" scp file1 host@remote

If you want to only save the log output if the command fails (as it appears you're trying to do in your code), then how about redirecting the output to a temporary file and then moving the file to the desired location afterwards? It might look something like this:

#!/bin/bash

# Set path the real log file location
LOG=/path/to/file.log

# Create a temporary file to capture standard output and standard error
TEMPLOG="$(mktemp)"

# Run command and redirect output to temporary logfile
2>"${TEMPLOG}" scp file1 host@remote

# do 'whatever' if scp command succeeds:
if [ $? = 0 ];
then
    echo 'Command successful!'

# else log both stdout/stderr to ${LOG} file
else
    # Move the log file from the temporary location to the desired location
    mv "${TEMPLOG}" "${LOG}"

    # DEBUG - print contents of ${LOG} var for testing purposes
    printf "${LOG}"
fi
  • Ok I see. I'd thought &> would've been handled in the same way $? is- the output of the last command. But I see now that $? is the "exit status", NOT "standard output" of the prev. command. Ok so this answers the main question, but would this be the most elegant solution for custom logging? Is there a way to avoid using temp files for output? I can see this getting out of hand in very large/multiple scripts or with multiple different log files. – Willman Nov 18 '17 at 16:30
  • @Willman I'm not sure what the most elegant way would be to implement logging in a shell script. If there's something better than just redirecting the output you want to a file then I haven't seen it. If you're on Linux then you might also consider using the logger command to write to the system logs, but I don't have any experience with that and I'm not sure that there would be any benefit to it. Maybe check out this post: Understand logging in Linux – igal Nov 18 '17 at 16:46
  • @Willman The mktemp command should make sure that there are no naming conflicts. Why would the temporary files get out of hand? To me it seems like the version of your script which temporary files is no less manageable than the the version of your script without them. – igal Nov 18 '17 at 16:47
  • Ok so &>"${LOG}" scp file host@remote successfully sends output to the /path/to/file.log like I want. This is the elegant solution I was talking about. I just didn't want to create unnecessary temp files on disk when I can just use I/O redirection and bash variables instead. (BTW to answer my side question- scp prints to stdout when there is a permissions issue on remote dir, not stderr for some reason). – Willman Nov 18 '17 at 16:57
  • @Willman Ah, ok. Based on the example code in your question I thought you only wanted to write the log file if the command failed. If you just wanted to log all of the output no matter what then that one-liner is definitely the most straight-forward thing to do. – igal Nov 18 '17 at 17:09
0

It looks like you were eventually satisfied with the outcome of this question but I propose something different.

#!/bin/bash
LOG=/path/to/file.log    
DEBUG=0 # 0 = True, 1 = False
OUTPUT=$(scp file1 host@remote 2>&1)

# do 'whatever' if scp command succeeds:
if [ $? -eq 0 ];
then
    echo "Success"
elif [[ DEBUG -eq 0 ]]
    # else log both stdout/stderr to ${LOG} file and user
    # DEBUG - Use tee to display ${LOG} contents efficiently
    printf "$OUTPUT" | tee $LOG
else
    # Send output to $LOG
    printf "$OUTPUT" >> $LOG
fi

Essentially capture STDIN/STDOUT in a variable no matter what, then if success do 'whatever' but if failure redirect STDIN/STDOUT to $LOG. Additionally with the $DEBUG flag you can tee the contents of $OUTPUT to the display and $LOG simultaneously.

Also for integer comparison you really should use -eq instead of = or ==

  • Note that the value of $? at the point where you access it will be the return code of the assignment DEBUG=0. $? will be non-zero if that assignment fails (which is possible if DEBUG is a read-only variable). Also note that your calls to echo will remove any newlines from the value $OUTPUT, and that there is also a then missing. – Kusalananda Apr 6 '18 at 14:49
  • Ah, yeah the $? is referencing the wrong thing, DEBUG was an afterthought but I'll fix it. echo does in fact remove the newlines so printf is better if you want to preserve. Which then are you referring to, it is on the line after the if statement just to be consistent with the original post? – Amos Baker Apr 6 '18 at 16:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.