Skip to content

Commit bee1290

Browse files
bitfieldoderwat"Masci, Richard (rx7322)"RyanGerard Webb
committed
Add Tee filter (fixes #145)
Co-authored-by: Hans Raaf <hara@oderwat.de> Co-authored-by: "Masci, Richard (rx7322)" <rx7322@us.att.com> Co-authored-by: Ryan <Emailryanmay@gmail.com> Co-authored-by: Gerard Webb <53147028+gedw99@users.noreply.github.com>
1 parent 10f067b commit bee1290

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ If you're already familiar with shell scripting and the Unix toolset, here is a
4848
| `sed` | [`Replace`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Replace) / [`ReplaceRegexp`](https://pkg.go.dev/github.com/bitfield/script#Pipe.ReplaceRegexp) |
4949
| `sha256sum` | [`SHA256Sum`](https://pkg.go.dev/github.com/bitfield/script#Pipe.SHA256Sum) / [`SHA256Sums`](https://pkg.go.dev/github.com/bitfield/script#Pipe.SHA256Sums) |
5050
| `tail` | [`Last`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Last) |
51+
| `tee` | [`Tee`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Tee) |
5152
| `uniq -c` | [`Freq`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Freq) |
5253
| `wc -l` | [`CountLines`](https://pkg.go.dev/github.com/bitfield/script#Pipe.CountLines) |
5354
| `xargs` | [`ExecForEach`](https://pkg.go.dev/github.com/bitfield/script#Pipe.ExecForEach) |
@@ -102,6 +103,12 @@ What's that? You want to append that output to a file instead of printing it to
102103
script.Args().Concat().Match("Error").First(10).AppendFile("/var/log/errors.txt")
103104
```
104105

106+
And if we'd like to send the output to the terminal *as well as* to the file, we can do that:
107+
108+
```go
109+
script.Echo("data").Tee().AppendFile("data.txt")
110+
```
111+
105112
We're not limited to getting data only from files or standard input. We can get it from HTTP requests too:
106113

107114
```go
@@ -304,6 +311,7 @@ Filters are methods on an existing pipe that also return a pipe, allowing you to
304311
| [`Replace`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Replace) | matching text replaced with given string |
305312
| [`ReplaceRegexp`](https://pkg.go.dev/github.com/bitfield/script#Pipe.ReplaceRegexp) | matching text replaced with given string |
306313
| [`SHA256Sums`](https://pkg.go.dev/github.com/bitfield/script#Pipe.SHA256Sums) | SHA-256 hashes of each listed file |
314+
| [`Tee`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Tee) | input copied to supplied writers |
307315

308316
Note that filters run concurrently, rather than producing nothing until each stage has fully read its input. This is convenient for executing long-running comands, for example. If you do need to wait for the pipeline to complete, call [`Wait`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Wait).
309317

@@ -328,6 +336,7 @@ Sinks are methods that return some data from a pipe, ending the pipeline and ext
328336

329337
| Version | New |
330338
| ----------- | ------- |
339+
| v0.22.0 | [`Tee`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Tee) |
331340
| v0.21.0 | HTTP support: [`Do`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Do), [`Get`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Get), [`Post`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Post) |
332341
| v0.20.0 | [`JQ`](https://pkg.go.dev/github.com/bitfield/script#Pipe.JQ) |
333342

script.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,23 @@ func (p *Pipe) String() (string, error) {
834834
return string(data), p.Error()
835835
}
836836

837+
// Tee copies the pipe's contents to each of the supplied writers, like Unix
838+
// tee(1). If no writers are supplied, the default is the pipe's standard
839+
// output.
840+
func (p *Pipe) Tee(writers ...io.Writer) *Pipe {
841+
if p.Error() != nil {
842+
return p
843+
}
844+
if len(writers) == 0 {
845+
writers = []io.Writer{p.stdout}
846+
}
847+
teeWriter := io.MultiWriter(writers...)
848+
return p.Filter(func(r io.Reader, w io.Writer) error {
849+
_, err := io.Copy(w, io.TeeReader(r, teeWriter))
850+
return err
851+
})
852+
}
853+
837854
// Wait reads the pipe to completion and discards the result. This is mostly
838855
// useful for waiting until concurrent filters have completed (see
839856
// [Pipe.Filter]).

script_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,39 @@ func TestSHA256Sums_OutputsCorrectHashForEachSpecifiedFile(t *testing.T) {
11041104
}
11051105
}
11061106

1107+
func TestTeeUsesConfiguredStdoutAsDefault(t *testing.T) {
1108+
t.Parallel()
1109+
buf := new(bytes.Buffer)
1110+
_, err := script.Echo("hello").WithStdout(buf).Tee().String()
1111+
if err != nil {
1112+
t.Fatal(err)
1113+
}
1114+
want := "hello"
1115+
got := buf.String()
1116+
if got != want {
1117+
t.Errorf("want %q, got %q", want, got)
1118+
}
1119+
}
1120+
1121+
func TestTeeWritesDataToSuppliedWritersAsWellAsToPipe(t *testing.T) {
1122+
t.Parallel()
1123+
buf1, buf2 := new(bytes.Buffer), new(bytes.Buffer)
1124+
got, err := script.Echo("hello world").Tee(buf1, buf2).String()
1125+
if err != nil {
1126+
t.Fatal(err)
1127+
}
1128+
want := "hello world"
1129+
if want != got {
1130+
t.Errorf("want %q on pipe, got %q", want, got)
1131+
}
1132+
if want != buf1.String() {
1133+
t.Errorf("want %q on writer 1, got %q", want, buf1.String())
1134+
}
1135+
if want != buf2.String() {
1136+
t.Errorf("want %q on writer 2, got %q", want, buf2.String())
1137+
}
1138+
}
1139+
11071140
func TestExecErrorsWhenTheSpecifiedCommandDoesNotExist(t *testing.T) {
11081141
t.Parallel()
11091142
p := script.Exec("doesntexist")
@@ -2186,6 +2219,32 @@ func ExamplePipe_String() {
21862219
// world
21872220
}
21882221

2222+
func ExamplePipe_Tee_stdout() {
2223+
s, err := script.Echo("hello\n").Tee().String()
2224+
if err != nil {
2225+
panic(err)
2226+
}
2227+
fmt.Println(s)
2228+
// Output:
2229+
// hello
2230+
// hello
2231+
}
2232+
2233+
func ExamplePipe_Tee_writers() {
2234+
buf1, buf2 := new(bytes.Buffer), new(bytes.Buffer)
2235+
s, err := script.Echo("hello\n").Tee(buf1, buf2).String()
2236+
if err != nil {
2237+
panic(err)
2238+
}
2239+
fmt.Print(s)
2240+
fmt.Print(buf1.String())
2241+
fmt.Print(buf2.String())
2242+
// Output:
2243+
// hello
2244+
// hello
2245+
// hello
2246+
}
2247+
21892248
func ExampleSlice() {
21902249
input := []string{"1", "2", "3"}
21912250
script.Slice(input).Stdout()

0 commit comments

Comments
 (0)