mirror of
https://github.com/minio/mc.git
synced 2025-11-10 13:42:32 +03:00
283 lines
8.6 KiB
Go
283 lines
8.6 KiB
Go
/*
|
|
* Minio Client (C) 2017 Minio, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
humanize "github.com/dustin/go-humanize"
|
|
"github.com/fatih/color"
|
|
"github.com/minio/cli"
|
|
"github.com/minio/mc/pkg/console"
|
|
"github.com/minio/mc/pkg/probe"
|
|
)
|
|
|
|
// List of all flags supported by find command.
|
|
var (
|
|
findFlags = []cli.Flag{
|
|
cli.StringFlag{
|
|
Name: "exec",
|
|
Usage: "Spawn an external process for each matching object (see FORMAT)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "ignore",
|
|
Usage: "Exclude objects matching the wildcard pattern",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "name",
|
|
Usage: "Find object names matching wildcard pattern",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "newer",
|
|
Usage: "Match all objects newer than specified time in units (see UNITS)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "older",
|
|
Usage: "Match all objects older than specified time in units (see UNITS)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "path",
|
|
Usage: "Match directory names matching wildcard pattern",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "print",
|
|
Usage: "Print in custom format to STDOUT (see FORMAT)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "regex",
|
|
Usage: "Match directory and object name with PCRE regex pattern",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "larger",
|
|
Usage: "Match all objects larger than specified size in units (see UNITS)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "smaller",
|
|
Usage: "Match all objects smaller than specified size in units (see UNITS)",
|
|
},
|
|
cli.UintFlag{
|
|
Name: "maxdepth",
|
|
Usage: "Limit directory navigation to specified depth",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "watch",
|
|
Usage: "Monitor a specified path for newly created files and objects",
|
|
},
|
|
}
|
|
)
|
|
|
|
var findCmd = cli.Command{
|
|
Name: "find",
|
|
Usage: "Search for files and objects.",
|
|
Action: mainFind,
|
|
Before: setGlobalsFromContext,
|
|
Flags: append(findFlags, globalFlags...),
|
|
CustomHelpTemplate: `NAME:
|
|
{{.HelpName}} - {{.Usage}}
|
|
|
|
USAGE:
|
|
{{.HelpName}} PATH [FLAGS]
|
|
|
|
FLAGS:
|
|
{{range .VisibleFlags}}{{.}}
|
|
{{end}}
|
|
UNITS
|
|
--smaller, --larger flags accept human-readable case-insensitive number
|
|
suffixes such as "k", "m", "g" and "t" referring to the metric units KB,
|
|
MB, GB and TB respectively. Adding an "i" to these prefixes, uses the IEC
|
|
units, so that "gi" refers to "gibibyte" or "GiB". A "b" at the end is
|
|
also accepted. Without suffixes the unit is bytes.
|
|
|
|
--older, --newer flags accept the suffixes "d", "w", "m" and "y" to refer
|
|
to units of days, weeks, months and years respectively. With the standard
|
|
rate of conversion being 7 days in 1 week, 30 days in 1 month, and 365
|
|
days in one year.
|
|
|
|
FORMAT
|
|
Support string substitutions with special interpretations for following keywords.
|
|
Keywords supported if target is filesystem or object storage:
|
|
|
|
{} --> Substitutes to full path.
|
|
{base} --> Substitutes to basename of path.
|
|
{dir} --> Substitutes to dirname of the path.
|
|
{size} --> Substitutes to file size of the path.
|
|
{time} --> Substitutes to file modified time of the path.
|
|
|
|
Keywords supported if target is object storage:
|
|
|
|
{url} --> Substitutes to a shareable URL of the path.
|
|
|
|
EXAMPLES:
|
|
01. Find all "foo.jpg" in all buckets under "s3" account.
|
|
$ {{.HelpName}} s3 --name "foo.jpg"
|
|
|
|
02. Find all objects with ".txt" extension under "s3/mybucket".
|
|
$ {{.HelpName}} s3/mybucket --name "*.txt"
|
|
|
|
03. Find only the object names without the directory component under "s3/mybucket".
|
|
$ {{.HelpName}} s3/mybucket --name "*" -print {base}
|
|
|
|
04. Find all images with ".jpg" extension under "s3/photos", prefixed with "album".
|
|
$ {{.HelpName}} s3/photos --name "*.jpg" --path "*/album*/*"
|
|
|
|
05. Find all images with ".jpg", ".png", and ".gif" extensions, using regex under "s3/photos".
|
|
$ {{.HelpName}} s3/photos --regex "(?i)\.(jpg|png|gif)$"
|
|
|
|
06. Find all images with ".jpg" extension under "s3/bucket" and copy to "play/bucket" *continuously*.
|
|
$ {{.HelpName}} s3/bucket --name "*.jpg" --watch --exec "mc cp {} play/bucket"
|
|
|
|
07. Find and generate public URLs valid for 7 days, for all objects between 64 MB, and 1 GB in size under "s3" account.
|
|
$ {{.HelpName}} s3 --larger 64MB --smaller 1GB --print {url}
|
|
|
|
08. Find all objects created in the last week under "s3/bucket".
|
|
$ {{.HelpName}} s3/bucket --newer 1w
|
|
|
|
09. Find all objects which were created more than 6 months ago, and exclude the ones with ".jpg"
|
|
extension under "s3".
|
|
$ {{.HelpName}} s3 --older 6m --ignore "*.jpg"
|
|
|
|
10. List all objects up to 3 levels sub-directory deep under "s3/bucket".
|
|
$ {{.HelpName}} s3/bucket --maxdepth 3
|
|
|
|
`,
|
|
}
|
|
|
|
// checkFindSyntax - validate the passed arguments
|
|
func checkFindSyntax(ctx *cli.Context) {
|
|
args := ctx.Args()
|
|
if !args.Present() {
|
|
args = []string{"./"} // No args just default to present directory.
|
|
} else if args.Get(0) == "." {
|
|
args[0] = "./" // If the arg is '.' treat it as './'.
|
|
}
|
|
|
|
for _, arg := range args {
|
|
if strings.TrimSpace(arg) == "" {
|
|
fatalIf(errInvalidArgument().Trace(args...), "Unable to validate empty argument.")
|
|
}
|
|
}
|
|
|
|
// Extract input URLs and validate.
|
|
for _, url := range args {
|
|
_, _, err := url2Stat(url)
|
|
if err != nil && !isURLPrefixExists(url, false) {
|
|
// Bucket name empty is a valid error for 'find myminio' unless we are using watch, treat it as such.
|
|
if _, ok := err.ToGoError().(BucketNameEmpty); ok && !ctx.Bool("watch") {
|
|
continue
|
|
}
|
|
fatalIf(err.Trace(url), "Unable to stat `"+url+"`.")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find context is container to hold all parsed input arguments,
|
|
// each parsed input is stored in its native typed form for
|
|
// ease of repurposing.
|
|
type findContext struct {
|
|
*cli.Context
|
|
execCmd string
|
|
ignorePattern string
|
|
namePattern string
|
|
pathPattern string
|
|
regexPattern string
|
|
maxDepth uint
|
|
printFmt string
|
|
olderThan time.Time
|
|
newerThan time.Time
|
|
largerSize uint64
|
|
smallerSize uint64
|
|
watch bool
|
|
|
|
// Internal values
|
|
targetAlias string
|
|
targetURL string
|
|
targetFullURL string
|
|
clnt Client
|
|
}
|
|
|
|
// mainFind - handler for mc find commands
|
|
func mainFind(ctx *cli.Context) error {
|
|
// Additional command specific theme customization.
|
|
console.SetColor("Find", color.New(color.FgGreen, color.Bold))
|
|
|
|
checkFindSyntax(ctx)
|
|
|
|
args := ctx.Args()
|
|
if !args.Present() {
|
|
args = []string{"./"} // Not args present default to present directory.
|
|
} else if args.Get(0) == "." {
|
|
args[0] = "./" // If the arg is '.' treat it as './'.
|
|
}
|
|
|
|
clnt, err := newClient(args[0])
|
|
fatalIf(err.Trace(args...), "Unable to initialize `"+args[0]+"`.")
|
|
|
|
var olderThan, newerThan time.Time
|
|
|
|
if ctx.String("older") != "" {
|
|
olderThan, err = parseTime(ctx.String("older"))
|
|
fatalIf(err.Trace(ctx.String("older")), "Unable to parse input time.")
|
|
}
|
|
if ctx.String("newer") != "" {
|
|
newerThan, err = parseTime(ctx.String("newer"))
|
|
fatalIf(err.Trace(ctx.String("newer")), "Unable to parse input time.")
|
|
}
|
|
|
|
// Use 'e' to indicate Go error, this is a convention followed in `mc`. For probe.Error we call it
|
|
// 'err' and regular Go error is called as 'e'.
|
|
var e error
|
|
var largerSize, smallerSize uint64
|
|
|
|
if ctx.String("larger") != "" {
|
|
largerSize, e = humanize.ParseBytes(ctx.String("larger"))
|
|
fatalIf(probe.NewError(e).Trace(ctx.String("larger")), "Unable to parse input bytes.")
|
|
}
|
|
|
|
if ctx.String("smaller") != "" {
|
|
smallerSize, e = humanize.ParseBytes(ctx.String("smaller"))
|
|
fatalIf(probe.NewError(e).Trace(ctx.String("smaller")), "Unable to parse input bytes.")
|
|
}
|
|
|
|
targetAlias, _, hostCfg, err := expandAlias(args[0])
|
|
fatalIf(err.Trace(args[0]), "Unable to expand alias.")
|
|
|
|
var targetFullURL string
|
|
if hostCfg != nil {
|
|
targetFullURL = hostCfg.URL
|
|
}
|
|
|
|
return doFind(&findContext{
|
|
Context: ctx,
|
|
maxDepth: ctx.Uint("maxdepth"),
|
|
execCmd: ctx.String("exec"),
|
|
printFmt: ctx.String("print"),
|
|
namePattern: ctx.String("name"),
|
|
pathPattern: ctx.String("path"),
|
|
regexPattern: ctx.String("regex"),
|
|
olderThan: olderThan,
|
|
newerThan: newerThan,
|
|
largerSize: largerSize,
|
|
smallerSize: smallerSize,
|
|
watch: ctx.Bool("watch"),
|
|
targetAlias: targetAlias,
|
|
targetURL: args[0],
|
|
targetFullURL: targetFullURL,
|
|
clnt: clnt,
|
|
})
|
|
}
|