script icon indicating copy to clipboard operation
script copied to clipboard

I don't want stderr

Open rmasci opened this issue 3 years ago • 7 comments

How do I turn off stderr. I want to run a command like:

ssh my.corporate.server.com get uptime

I just want the uptime -- I don't want all the bs that goes with logging into a server via ssh. My company likes to stick on ALL it's servers a message of the day -- and this come out in the command when I run it using script.Exec("ssh my.corporate.server.com get uptime").String() The result is


This system is restricted solely to the company authorized users for legitimate
business purposes only. The actual or attempted unauthorized access, use, or
modification of this system is strictly prohibited by the company. Unauthorized users
are subject to Company disciplinary proceedings and/or criminal and civil
penalties under state, federal, or other applicable domestic and foreign laws.
The use of this system may be monitored and recorded for administrative and
security reasons. Anyone accessing this system expressly consents to such
monitoring and is advised that if monitoring reveals possible evidence of
criminal activity, The company may provide the evidence of such activity to law
enforcement officials. All users must comply with the company company policies
regarding the protection of the company information assets.
   _________________________________________________________________
 20:51:17 up 36 days,  4:25,  1 user,  load average: 2.52, 2.29, 2.21```

rmasci avatar Aug 10 '22 20:08 rmasci

Thanks, @rmasci! Are you saying that you'd like to be able to get either the command's standard output or standard error separately? How would you change this program to do that?

script.Exec("ssh my.corporate.server.com get uptime").String() 

bitfield avatar Aug 11 '22 08:08 bitfield

I wouldn't want to break the existing Exec statements, but if we look at fmt.Printf, it uses 'a ...any' as an interface, and by doing this you don't have to specify options to 'a ...any' fmt.Printf("Hello\n") works just fine without any options. What if the exec -- (and others) added an interface, which would be an optional parameter you can specify? Take this example from the playground: https://go.dev/play/p/CYFNGcftYwZ

Thanks, Rich

rmasci avatar Aug 11 '22 14:08 rmasci

Great, so the syntax for this would look something like:

script.Exec("ssh mybox.com uptime", "nostderr").Stdout()

That's okay, though let's see what other suggestions there are.

Referencing your original use case, though, I'm puzzled: the ssh on my system doesn't print the MOTD when executed with a command anyway:

$ ssh mybox.com uptime
 16:44:04 up 757 days,  5:27,  0 users,  load average: 2.03, 1.37, 1.50

$ ssh mybox.com
Welcome to my box! The current CPU temperature is 89ºC.

And when it does print it, it doesn't go to standard error. So I'm not sure even this proposed change would help you there.

bitfield avatar Aug 11 '22 16:08 bitfield

Wouldn't

script.Exec("ssh my.corporate.server.com get uptime").Last().String()

do the trick for this particular example?

I appreciate there may be other cases where the stderr issue arises - but for this one example - there seems to be an immediate path forward.

wishdev avatar Aug 18 '22 22:08 wishdev

Right, but as I mentioned, it's even simpler than that:

script.Exec("ssh my.corporate.server.com uptime").Stdout()

works just fine.

bitfield avatar Aug 19 '22 08:08 bitfield

The way we do ssh MOTD in my company is through the /etc/sshd_config file, should have mentioned that before. In that config file there is a line Banner '/etc/issue.net'.

script.Exec("ssh my.corporate.server.com uptime").Stdout() results in getting everything.

In this example, i used 'uptime' as the program I want to exec -- and yes you could just get the last line or the last few lines, but what if you don't know the length of your output? The program I am writing isn't for just one command, it could be many commands to include sql, csv, html. To make matters worse the MOTD on all the systems is not always the same, it changes, so I can't just filter it out. In other programs I just turn off the stderr and it works fine. In this program Script would allow me to simplify the code.

Most of the time, id say 95% of my scripts / programs stdout and stderr can be piped together, but there have been many instances where I've seen things I run put messages like warning messages (that can be ignored) on stderr -- so I just filter those out by piping stderr to /dev/null.

I just think having a way to add in control over stdout / stderr would be very useful. What would be really cool (and I am getting off topic) is to be able to turn stdout, stderr, and stdin to the end user. I've done this before with Go where I exec a command, and then turn control back to the user at a certain point.

rmasci avatar Aug 19 '22 19:08 rmasci

I think that turning off stderr altogether is probably a bad idea: then you won't see actual errors.

Maybe the stderr output could be captured, and reported by the pipe's Error() method.

bitfield avatar Aug 20 '22 07:08 bitfield

Did anything ever come of this? I'm considering use the package, but all of my command line apps produce both actual data to stdout and progress/error reporting to stderr. I don't see any of your examples explaining how to process stderr.

tjayrush avatar Sep 22 '22 23:09 tjayrush

Could you give an example of the kind of program you'd want to write, @tjayrush? The design problem here is basically that there's only one stream of data in a pipe, so it's not clear what the code would look like if there were two streams to read separately.

bitfield avatar Sep 23 '22 08:09 bitfield

It's been a few months, and I thought I'd chime back in with what I was able to work out. I don't consider myself the greatest programmer so I thought I'd submit it here -- if you want a pull request I could do that. If this is junk and there is something unforeseen that I didn't work out, we can just let it be. I just thought having the ability to redirect stdout would be a good thing.

So what I did was modify script.go, to the Pipe struct I added stderr io.Writer:

type Pipe struct {
	Reader ReadAutoCloser
	stdout io.Writer
	stderr io.Writer

Then created a new method on Pipe that sets stderr to an io.Writer. Then created a method to set stderr:

// SetStderr stderr sets the pipe's standard error to the specified reader. If this is not set, stdout / stderr are combined.
// You can then write the stderr to a log file or just open /dev/null and write the output there.
func (p *Pipe) SetStderr(w io.Writer) {
	p.stderr = w
}

Then modified the exec method. Here it tests to see if p.stderr is not nil, then set cmd.Stderr to p.stderr.

func (p *Pipe) Exec(command string) *Pipe {
	return p.Filter(func(r io.Reader, w io.Writer) error {
		args, ok := shell.Split(command) // strings.Fields doesn't handle quotes
		if !ok {
			return fmt.Errorf("unbalanced quotes or backslashes in [%s]", command)
		}
		cmd := exec.Command(args[0], args[1:]...)
		cmd.Stdin = r
		cmd.Stdout = w
		if p.stderr != nil {
			cmd.Stderr = p.stderr
		} else {
			cmd.Stderr = w
		}
		err := cmd.Start()
		if err != nil {
			fmt.Fprintln(w, err)
			return err
		}
		return cmd.Wait()
	})
}

I'd write this up with tests but I've not learned go tests yet. I did write a short program below using ssh that works for me. In this example I wrote the output to a log file. You could replace exec.log with /dev/null and toss the errors away, but truncate a file in /tmp rather than writing it to /dev/null. At least that way you could see the output of the last run.
If the user wants to redirect stderr -- create a pipe first, use the SetStderr method, then p.Exec(cmd). just doing script.Exec(cmd) would use combined. Reminder: To get ssh to print a MOTD to stderr, set in /etc/ssh/sshd_config 'Banner /etc/yourmotd'

package main

import (
	"fmt"
	"os"

	"github.com/bitfield/script"
)

func main() {
	w, err := os.OpenFile("exec.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 660)
	if err != nil {
		panic(err)
	}
	p := script.NewPipe()
	p.SetStderr(w)
	s, err := p.Exec("ssh myworkserver uptime").String()
	if err != nil {
		panic(err)
	}
	fmt.Println(s)
}

I thought the user could either put the stderr into a log file or just open /dev/null as a file and write it there.

rmasci avatar Jan 05 '23 00:01 rmasci

Do we have a solution to separate stdout and stderr? I need these two streams separated too.

Robert-M-Muench avatar Mar 08 '23 18:03 Robert-M-Muench

I do have one. I've been wanting to issue a pull request to this repository but not sure if that's what is wanted. You can try it out: https://github.com/rmasci/script

To use you've got to define the pipe using script.NewPipe. Then there is a command I added to p.SetStderr(io.discard), from there it should remove all stderr.

pipe:= script.NewPipe()
pipe.SetStderr(io.Discard)
pipe.Exec("scp /path/to/myfile mycorpserver:/path/to/destination").Stdout()

pipe.SetStderr takes an io.writer so if you want to redirect it to a file that is possible too.

Also I added this to go test -v: --- PASS: TestSetStderrDoesntPrintStderr (0.16s)

rmasci avatar Mar 08 '23 19:03 rmasci

Thanks for the link. I am going to check it out.

Robert-M-Muench avatar Mar 09 '23 11:03 Robert-M-Muench