diff --git a/cmd/all/all.go b/cmd/all/all.go index 92d9ac1cc..51f427fee 100644 --- a/cmd/all/all.go +++ b/cmd/all/all.go @@ -5,6 +5,7 @@ import ( // Active commands _ "github.com/ncw/rclone/cmd" _ "github.com/ncw/rclone/cmd/authorize" + _ "github.com/ncw/rclone/cmd/cat" _ "github.com/ncw/rclone/cmd/check" _ "github.com/ncw/rclone/cmd/cleanup" _ "github.com/ncw/rclone/cmd/config" diff --git a/cmd/cat/cat.go b/cmd/cat/cat.go new file mode 100644 index 000000000..2b8c7b625 --- /dev/null +++ b/cmd/cat/cat.go @@ -0,0 +1,40 @@ +package cat + +import ( + "os" + + "github.com/ncw/rclone/cmd" + "github.com/ncw/rclone/fs" + "github.com/spf13/cobra" +) + +func init() { + cmd.Root.AddCommand(catCmd) +} + +var catCmd = &cobra.Command{ + Use: "cat remote:path", + Short: `Concatenates any files and sends them to stdout.`, + Long: ` +rclone cat sends any files to standard output. + +You can use it like this to output a single file + + rclone cat remote:path/to/file + +Or like this to output any file in dir or subdirectories. + + rclone cat remote:path/to/dir + +Or like this to output any .txt files in dir or subdirectories. + + rclone --include "*.txt" cat remote:path/to/dir +`, + Run: func(command *cobra.Command, args []string) { + cmd.CheckArgs(1, 1, command, args) + fsrc := cmd.NewFsSrc(args) + cmd.Run(false, command, func() error { + return fs.Cat(fsrc, os.Stdout) + }) + }, +} diff --git a/fs/operations.go b/fs/operations.go index 5740ac37d..8ea58e8bd 100644 --- a/fs/operations.go +++ b/fs/operations.go @@ -1012,3 +1012,34 @@ func CleanUp(f Fs) error { } return fc.CleanUp() } + +// Cat any files to the io.Writer +func Cat(f Fs, w io.Writer) error { + var mu sync.Mutex + return ListFn(f, func(o Object) { + Stats.Transferring(o.Remote()) + defer Stats.DoneTransferring(o.Remote()) + mu.Lock() + defer mu.Unlock() + in, err := o.Open() + if err != nil { + Stats.Error() + ErrorLog(o, "Failed to open: %v", err) + return + } + defer func() { + err = in.Close() + if err != nil { + Stats.Error() + ErrorLog(o, "Failed to close: %v", err) + } + }() + inAccounted := NewAccount(in, o) // account the transfer + _, err = io.Copy(w, inAccounted) + if err != nil { + Stats.Error() + ErrorLog(o, "Failed to send to output: %v", err) + } + }) + +} diff --git a/fs/operations_test.go b/fs/operations_test.go index a1700e690..9ed727e06 100644 --- a/fs/operations_test.go +++ b/fs/operations_test.go @@ -620,3 +620,21 @@ func TestDeduplicateRename(t *testing.T) { } } } + +func TestCat(t *testing.T) { + r := NewRun(t) + defer r.Finalise() + file1 := r.WriteBoth("file1", "aaa", t1) + file2 := r.WriteBoth("file2", "bbb", t2) + + fstest.CheckItems(t, r.fremote, file1, file2) + + var buf bytes.Buffer + err := fs.Cat(r.fremote, &buf) + require.NoError(t, err) + res := buf.String() + + if res != "aaabbb" && res != "bbbaaa" { + t.Errorf("Incorrect output from Cat: %q", res) + } +}