//go:build !plan9 // Package list implements 'rclone archive list' package list import ( "context" "fmt" "os" "strings" "github.com/mholt/archives" "github.com/rclone/rclone/cmd" "github.com/rclone/rclone/cmd/archive" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/accounting" "github.com/rclone/rclone/fs/config/flags" "github.com/rclone/rclone/fs/filter" "github.com/rclone/rclone/fs/operations" "github.com/spf13/cobra" ) var ( longList = false plainList = false filesOnly = false dirsOnly = false ) func init() { flagSet := Command.Flags() flags.BoolVarP(flagSet, &longList, "long", "", longList, "List extra attributtes", "") flags.BoolVarP(flagSet, &plainList, "plain", "", plainList, "Only list file names", "") flags.BoolVarP(flagSet, &filesOnly, "files-only", "", false, "Only list files", "") flags.BoolVarP(flagSet, &dirsOnly, "dirs-only", "", false, "Only list directories", "") archive.Command.AddCommand(Command) } // Command - list var Command = &cobra.Command{ Use: "list [flags] ", Short: `List archive contents from source.`, Long: strings.ReplaceAll(` List the contents of an archive to the console, auto detecting the format. See [rclone archive create](/commands/rclone_archive_create/) for the archive formats supported. For example: ||| $ rclone archive list remote:archive.zip 6 file.txt 0 dir/ 4 dir/bye.txt ||| Or with |--long| flag for more info: ||| $ rclone archive list --long remote:archive.zip 6 2025-10-30 09:46:23.000000000 file.txt 0 2025-10-30 09:46:57.000000000 dir/ 4 2025-10-30 09:46:57.000000000 dir/bye.txt ||| Or with |--plain| flag which is useful for scripting: ||| $ rclone archive list --plain /path/to/archive.zip file.txt dir/ dir/bye.txt ||| Or with |--dirs-only|: ||| $ rclone archive list --plain --dirs-only /path/to/archive.zip dir/ ||| Or with |--files-only|: ||| $ rclone archive list --plain --files-only /path/to/archive.zip file.txt dir/bye.txt ||| Filters may also be used: ||| $ rclone archive list --long archive.zip --include "bye.*" 4 2025-10-30 09:46:57.000000000 dir/bye.txt ||| The [archive backend](/archive/) can also be used to list files. It can be used to read only mount archives also but it supports a different set of archive formats to the archive commands. `, "|", "`"), Annotations: map[string]string{ "versionIntroduced": "v1.72", }, RunE: func(command *cobra.Command, args []string) error { cmd.CheckArgs(1, 1, command, args) src, srcFile := cmd.NewFsFile(args[0]) cmd.Run(false, false, command, func() error { return ArchiveList(context.Background(), src, srcFile, listFile) }) return nil }, } func listFile(ctx context.Context, f archives.FileInfo) error { ci := fs.GetConfig(ctx) fi := filter.GetConfig(ctx) // check if excluded if !fi.Include(f.NameInArchive, f.Size(), f.ModTime(), fs.Metadata{}) { return nil } if filesOnly && f.IsDir() { return nil } if dirsOnly && !f.IsDir() { return nil } // get entry name name := f.NameInArchive if f.IsDir() && !strings.HasSuffix(name, "/") { name += "/" } // print info if longList { operations.SyncFprintf(os.Stdout, "%s %s %s\n", operations.SizeStringField(f.Size(), ci.HumanReadable, 9), f.ModTime().Format("2006-01-02 15:04:05.000000000"), name) } else if plainList { operations.SyncFprintf(os.Stdout, "%s\n", name) } else { operations.SyncFprintf(os.Stdout, "%s %s\n", operations.SizeStringField(f.Size(), ci.HumanReadable, 9), name) } return nil } // ArchiveList -- print a list of the files in the archive func ArchiveList(ctx context.Context, src fs.Fs, srcFile string, listFn archives.FileHandler) error { var srcObj fs.Object var err error // get object srcObj, err = src.NewObject(ctx, srcFile) if err != nil { return fmt.Errorf("source is not a file, %w", err) } fs.Debugf(nil, "Source archive file: %s/%s", src.Root(), srcFile) // start accounting tr := accounting.Stats(ctx).NewTransfer(srcObj, nil) defer func() { tr.Done(ctx, err) }() // open source var options []fs.OpenOption for _, option := range fs.GetConfig(ctx).DownloadHeaders { options = append(options, option) } in0, err := operations.Open(ctx, srcObj, options...) if err != nil { return fmt.Errorf("failed to open file %s: %w", srcFile, err) } // account and buffer the transfer // in = tr.Account(ctx, in).WithBuffer() in := tr.Account(ctx, in0) // identify format format, _, err := archives.Identify(ctx, "", in) if err != nil { return fmt.Errorf("failed to open check file type, %w", err) } fs.Debugf(nil, "Listing %s/%s, format %s", src.Root(), srcFile, strings.TrimPrefix(format.Extension(), ".")) // check if extract is supported by format ex, isExtract := format.(archives.Extraction) if !isExtract { return fmt.Errorf("extraction for %s not supported", strings.TrimPrefix(format.Extension(), ".")) } // list files return ex.Extract(ctx, in, listFn) }