golden hour
/opt/saltstack/salt/lib/python3.10/site-packages/salt/utils
⬆️ Go Up
Upload
File/Folder
Size
Actions
__init__.py
237 B
Del
OK
__pycache__
-
Del
OK
aggregation.py
5.17 KB
Del
OK
ansible.py
1.48 KB
Del
OK
args.py
18.33 KB
Del
OK
asynchronous.py
4.06 KB
Del
OK
atomicfile.py
5.33 KB
Del
OK
aws.py
20.37 KB
Del
OK
azurearm.py
11.42 KB
Del
OK
beacons.py
517 B
Del
OK
boto3mod.py
8.36 KB
Del
OK
boto_elb_tag.py
3.02 KB
Del
OK
botomod.py
7.98 KB
Del
OK
cache.py
11.49 KB
Del
OK
channel.py
489 B
Del
OK
cloud.py
116.3 KB
Del
OK
color.py
2.72 KB
Del
OK
compat.py
1.89 KB
Del
OK
configcomparer.py
3.88 KB
Del
OK
configparser.py
10.82 KB
Del
OK
context.py
6.8 KB
Del
OK
crypt.py
5 KB
Del
OK
ctx.py
1.42 KB
Del
OK
data.py
53.03 KB
Del
OK
dateutils.py
2.3 KB
Del
OK
debug.py
4.19 KB
Del
OK
decorators
-
Del
OK
dictdiffer.py
16.48 KB
Del
OK
dicttrim.py
3.9 KB
Del
OK
dictupdate.py
11.33 KB
Del
OK
dns.py
35.21 KB
Del
OK
doc.py
2.25 KB
Del
OK
dockermod
-
Del
OK
entrypoints.py
1.83 KB
Del
OK
environment.py
2.2 KB
Del
OK
error.py
1.18 KB
Del
OK
etcd_util.py
33.25 KB
Del
OK
event.py
52.45 KB
Del
OK
extend.py
8.87 KB
Del
OK
extmods.py
6.04 KB
Del
OK
filebuffer.py
3.15 KB
Del
OK
files.py
27.94 KB
Del
OK
find.py
22.08 KB
Del
OK
fsutils.py
3.29 KB
Del
OK
functools.py
6.02 KB
Del
OK
gitfs.py
130.41 KB
Del
OK
github.py
1.52 KB
Del
OK
gzip_util.py
2.86 KB
Del
OK
hashutils.py
5.91 KB
Del
OK
http.py
33.9 KB
Del
OK
iam.py
1.22 KB
Del
OK
icinga2.py
754 B
Del
OK
idem.py
1.22 KB
Del
OK
immutabletypes.py
2.46 KB
Del
OK
itertools.py
2.36 KB
Del
OK
jid.py
3 KB
Del
OK
jinja.py
33.92 KB
Del
OK
job.py
6.89 KB
Del
OK
json.py
3.78 KB
Del
OK
kickstart.py
41.04 KB
Del
OK
kinds.py
493 B
Del
OK
lazy.py
3.06 KB
Del
OK
listdiffer.py
10.9 KB
Del
OK
locales.py
2.06 KB
Del
OK
mac_utils.py
14.01 KB
Del
OK
mako.py
3.97 KB
Del
OK
master.py
29.7 KB
Del
OK
mattermost.py
1.77 KB
Del
OK
memcached.py
3.56 KB
Del
OK
migrations.py
1.46 KB
Del
OK
mine.py
3.68 KB
Del
OK
minion.py
4.13 KB
Del
OK
minions.py
43.38 KB
Del
OK
mount.py
1.15 KB
Del
OK
msazure.py
5.36 KB
Del
OK
msgpack.py
4.69 KB
Del
OK
nacl.py
13.65 KB
Del
OK
namecheap.py
4.32 KB
Del
OK
napalm.py
23.22 KB
Del
OK
nb_popen.py
7.24 KB
Del
OK
network.py
73.86 KB
Del
OK
nxos.py
12.94 KB
Del
OK
nxos_api.py
4 KB
Del
OK
odict.py
13.21 KB
Del
OK
openstack
-
Del
OK
oset.py
6.41 KB
Del
OK
pagerduty.py
3.03 KB
Del
OK
parsers.py
122.69 KB
Del
OK
path.py
11.24 KB
Del
OK
pbm.py
9.81 KB
Del
OK
pkg
-
Del
OK
platform.py
5.84 KB
Del
OK
powershell.py
4.15 KB
Del
OK
preseed.py
2.64 KB
Del
OK
process.py
40.76 KB
Del
OK
profile.py
3.21 KB
Del
OK
proxy.py
331 B
Del
OK
psutil_compat.py
3.63 KB
Del
OK
pushover.py
4.51 KB
Del
OK
pycrypto.py
5.41 KB
Del
OK
pydsl.py
13.74 KB
Del
OK
pyobjects.py
10.75 KB
Del
OK
reactor.py
18.99 KB
Del
OK
reclass.py
752 B
Del
OK
roster_matcher.py
3.55 KB
Del
OK
rsax931.py
8.42 KB
Del
OK
s3.py
8.78 KB
Del
OK
saltclass.py
14.27 KB
Del
OK
sanitizers.py
2.51 KB
Del
OK
schedule.py
71.81 KB
Del
OK
schema.py
54.26 KB
Del
OK
sdb.py
4.04 KB
Del
OK
slack.py
3.58 KB
Del
OK
smb.py
11.16 KB
Del
OK
smtp.py
3.27 KB
Del
OK
ssdp.py
14.75 KB
Del
OK
ssh.py
769 B
Del
OK
state.py
8.43 KB
Del
OK
stringio.py
355 B
Del
OK
stringutils.py
16.95 KB
Del
OK
systemd.py
5.51 KB
Del
OK
templates.py
24.03 KB
Del
OK
textformat.py
5.03 KB
Del
OK
thin.py
31.91 KB
Del
OK
timed_subprocess.py
4.06 KB
Del
OK
timeout.py
1.53 KB
Del
OK
timeutil.py
2.4 KB
Del
OK
url.py
5 KB
Del
OK
user.py
11.86 KB
Del
OK
validate
-
Del
OK
value.py
247 B
Del
OK
vault.py
21.74 KB
Del
OK
verify.py
25.34 KB
Del
OK
versions.py
17.17 KB
Del
OK
virt.py
3.24 KB
Del
OK
virtualbox.py
22.43 KB
Del
OK
vmware.py
129.74 KB
Del
OK
vsan.py
17.18 KB
Del
OK
vt.py
31.47 KB
Del
OK
vt_helper.py
4.4 KB
Del
OK
win_chcp.py
3.7 KB
Del
OK
win_dacl.py
95.49 KB
Del
OK
win_dotnet.py
4.74 KB
Del
OK
win_functions.py
12.69 KB
Del
OK
win_lgpo_auditpol.py
8.48 KB
Del
OK
win_lgpo_netsh.py
17.87 KB
Del
OK
win_lgpo_reg.py
16.98 KB
Del
OK
win_network.py
16.35 KB
Del
OK
win_osinfo.py
2.83 KB
Del
OK
win_pdh.py
13.85 KB
Del
OK
win_reg.py
30.82 KB
Del
OK
win_runas.py
10.53 KB
Del
OK
win_service.py
5.2 KB
Del
OK
win_system.py
14.47 KB
Del
OK
win_update.py
40.36 KB
Del
OK
winapi.py
818 B
Del
OK
x509.py
73.19 KB
Del
OK
xdg.py
316 B
Del
OK
xmlutil.py
13.91 KB
Del
OK
yaml.py
349 B
Del
OK
yamldumper.py
3.37 KB
Del
OK
yamlencoding.py
1.55 KB
Del
OK
yamllint.py
1.61 KB
Del
OK
yamlloader.py
6.04 KB
Del
OK
yamlloader_old.py
8.15 KB
Del
OK
yast.py
619 B
Del
OK
zeromq.py
1.74 KB
Del
OK
zfs.py
19.15 KB
Del
OK
Edit: find.py
""" Approximate the Unix find(1) command and return a list of paths that meet the specified criteria. The options include match criteria: name = file-glob # case sensitive iname = file-glob # case insensitive regex = file-regex # case sensitive iregex = file-regex # case insensitive type = file-types # match any listed type user = users # match any listed user group = groups # match any listed group size = [+-]number[size-unit] # default unit = byte mtime = interval # modified since date grep = regex # search file contents and/or actions: delete [= file-types] # default type = 'f' exec = command [arg ...] # where {} is replaced by pathname print [= print-opts] and/or depth criteria: maxdepth = maximum depth to transverse in path mindepth = minimum depth to transverse before checking files or directories The default action is 'print=path'. file-glob: * = match zero or more chars ? = match any char [abc] = match a, b, or c [!abc] or [^abc] = match anything except a, b, and c [x-y] = match chars x through y [!x-y] or [^x-y] = match anything except chars x through y {a,b,c} = match a or b or c file-regex: a Python re (regular expression) pattern file-types: a string of one or more of the following: a: all file types b: block device c: character device d: directory p: FIFO (named pipe) f: plain file l: symlink s: socket users: a space and/or comma separated list of user names and/or uids groups: a space and/or comma separated list of group names and/or gids size-unit: b: bytes k: kilobytes m: megabytes g: gigabytes t: terabytes interval: [<num>w] [<num>[d]] [<num>h] [<num>m] [<num>s] where: w: week d: day h: hour m: minute s: second print-opts: a comma and/or space separated list of one or more of the following: group: group name md5: MD5 digest of file contents mode: file permissions (as as integer) mtime: last modification time (as time_t) name: file basename path: file absolute path size: file size in bytes type: file type user: user name """ import logging import os import re import shutil import stat import sys import time from subprocess import PIPE, Popen import salt.defaults.exitcodes import salt.utils.args import salt.utils.hashutils import salt.utils.path import salt.utils.stringutils from salt.utils.filebuffer import BufferedReader try: import grp import pwd # TODO: grp and pwd are both used in the code, we better make sure that # that code never gets run if importing them does not succeed except ImportError: pass # Set up logger log = logging.getLogger(__name__) _REQUIRES_PATH = 1 _REQUIRES_STAT = 2 _REQUIRES_CONTENTS = 4 _FILE_TYPES = { "b": stat.S_IFBLK, "c": stat.S_IFCHR, "d": stat.S_IFDIR, "f": stat.S_IFREG, "l": stat.S_IFLNK, "p": stat.S_IFIFO, "s": stat.S_IFSOCK, stat.S_IFBLK: "b", stat.S_IFCHR: "c", stat.S_IFDIR: "d", stat.S_IFREG: "f", stat.S_IFLNK: "l", stat.S_IFIFO: "p", stat.S_IFSOCK: "s", } _INTERVAL_REGEX = re.compile( r""" ^\s* (?P<modifier>[+-]?) (?: (?P<week> \d+ (?:\.\d*)? ) \s* [wW] )? \s* (?: (?P<day> \d+ (?:\.\d*)? ) \s* [dD] )? \s* (?: (?P<hour> \d+ (?:\.\d*)? ) \s* [hH] )? \s* (?: (?P<minute> \d+ (?:\.\d*)? ) \s* [mM] )? \s* (?: (?P<second> \d+ (?:\.\d*)? ) \s* [sS] )? \s* $ """, flags=re.VERBOSE, ) _PATH_DEPTH_IGNORED = (os.path.sep, os.path.curdir, os.path.pardir) def _parse_interval(value): """ Convert an interval string like 1w3d6h into the number of seconds, time resolution (1 unit of the smallest specified time unit) and the modifier( '+', '-', or ''). w = week d = day h = hour m = minute s = second """ match = _INTERVAL_REGEX.match(str(value)) if match is None: raise ValueError("invalid time interval: '{}'".format(value)) result = 0 resolution = None for name, multiplier in [ ("second", 1), ("minute", 60), ("hour", 60 * 60), ("day", 60 * 60 * 24), ("week", 60 * 60 * 24 * 7), ]: if match.group(name) is not None: result += float(match.group(name)) * multiplier if resolution is None: resolution = multiplier return result, resolution, match.group("modifier") def _parse_size(value): scalar = value.strip() if scalar.startswith(("-", "+")): style = scalar[0] scalar = scalar[1:] else: style = "=" if scalar: multiplier = { "b": 2**0, "k": 2**10, "m": 2**20, "g": 2**30, "t": 2**40, }.get(scalar[-1].lower()) if multiplier: scalar = scalar[:-1].strip() else: multiplier = 1 else: multiplier = 1 try: num = int(scalar) * multiplier except ValueError: try: num = int(float(scalar) * multiplier) except ValueError: raise ValueError('invalid size: "{}"'.format(value)) if style == "-": min_size = 0 max_size = num elif style == "+": min_size = num max_size = sys.maxsize else: min_size = num max_size = num + multiplier - 1 return min_size, max_size class Option: """ Abstract base class for all find options. """ def requires(self): return _REQUIRES_PATH class NameOption(Option): """ Match files with a case-sensitive glob filename pattern. Note: this is the 'basename' portion of a pathname. The option name is 'name', e.g. {'name' : '*.txt'}. """ def __init__(self, key, value): self.regex = re.compile( value.replace(".", "\\.").replace("?", ".?").replace("*", ".*") + "$" ) def match(self, dirname, filename, fstat): return self.regex.match(filename) class InameOption(Option): """ Match files with a case-insensitive glob filename pattern. Note: this is the 'basename' portion of a pathname. The option name is 'iname', e.g. {'iname' : '*.TXT'}. """ def __init__(self, key, value): self.regex = re.compile( value.replace(".", "\\.").replace("?", ".?").replace("*", ".*") + "$", re.IGNORECASE, ) def match(self, dirname, filename, fstat): return self.regex.match(filename) class RegexOption(Option): """ Match files with a case-sensitive regular expression. Note: this is the 'basename' portion of a pathname. The option name is 'regex', e.g. {'regex' : '.*\\.txt'}. """ def __init__(self, key, value): try: self.regex = re.compile(value) except re.error: raise ValueError('invalid regular expression: "{}"'.format(value)) def match(self, dirname, filename, fstat): return self.regex.match(filename) class IregexOption(Option): """ Match files with a case-insensitive regular expression. Note: this is the 'basename' portion of a pathname. The option name is 'iregex', e.g. {'iregex' : '.*\\.txt'}. """ def __init__(self, key, value): try: self.regex = re.compile(value, re.IGNORECASE) except re.error: raise ValueError('invalid regular expression: "{}"'.format(value)) def match(self, dirname, filename, fstat): return self.regex.match(filename) class TypeOption(Option): """ Match files by their file type(s). The file type(s) are specified as an optionally comma and/or space separated list of letters. b = block device c = character device d = directory f = regular (plain) file l = symbolic link p = FIFO (named pipe) s = socket The option name is 'type', e.g. {'type' : 'd'} or {'type' : 'bc'}. """ def __init__(self, key, value): # remove whitespace and commas value = "".join(value.strip().replace(",", "").split()) self.ftypes = set() for ftype in value: try: self.ftypes.add(_FILE_TYPES[ftype]) except KeyError: raise ValueError('invalid file type "{}"'.format(ftype)) def requires(self): return _REQUIRES_STAT def match(self, dirname, filename, fstat): return stat.S_IFMT(fstat[stat.ST_MODE]) in self.ftypes class OwnerOption(Option): """ Match files by their owner name(s) and/or uid(s), e.g. 'root'. The names are a space and/or comma separated list of names and/or integers. A match occurs when the file's uid matches any user specified. The option name is 'owner', e.g. {'owner' : 'root'}. """ def __init__(self, key, value): self.uids = set() for name in value.replace(",", " ").split(): if name.isdigit(): self.uids.add(int(name)) else: try: self.uids.add(pwd.getpwnam(value).pw_uid) except KeyError: raise ValueError('no such user "{}"'.format(name)) def requires(self): return _REQUIRES_STAT def match(self, dirname, filename, fstat): return fstat[stat.ST_UID] in self.uids class GroupOption(Option): """ Match files by their group name(s) and/or uid(s), e.g. 'admin'. The names are a space and/or comma separated list of names and/or integers. A match occurs when the file's gid matches any group specified. The option name is 'group', e.g. {'group' : 'admin'}. """ def __init__(self, key, value): self.gids = set() for name in value.replace(",", " ").split(): if name.isdigit(): self.gids.add(int(name)) else: try: self.gids.add(grp.getgrnam(name).gr_gid) except KeyError: raise ValueError('no such group "{}"'.format(name)) def requires(self): return _REQUIRES_STAT def match(self, dirname, filename, fstat): return fstat[stat.ST_GID] in self.gids class SizeOption(Option): """ Match files by their size. Prefix the size with '-' to find files the specified size and smaller. Prefix the size with '+' to find files the specified size and larger. Without the +/- prefix, match the exact file size. The size can be suffixed with (case-insensitive) suffixes: b = bytes k = kilobytes m = megabytes g = gigabytes t = terabytes The option name is 'size', e.g. {'size' : '+1G'}. """ def __init__(self, key, value): self.min_size, self.max_size = _parse_size(value) def requires(self): return _REQUIRES_STAT def match(self, dirname, filename, fstat): return self.min_size <= fstat[stat.ST_SIZE] <= self.max_size class MtimeOption(Option): """ Match files modified since the specified time. The option name is 'mtime', e.g. {'mtime' : '3d'}. The value format is [<num>w] [<num>[d]] [<num>h] [<num>m] [<num>s] where num is an integer or float and the case-insensitive suffixes are: w = week d = day h = hour m = minute s = second Whitespace is ignored in the value. """ def __init__(self, key, value): secs, resolution, modifier = _parse_interval(value) self.mtime = time.time() - int(secs / resolution) * resolution self.modifier = modifier def requires(self): return _REQUIRES_STAT def match(self, dirname, filename, fstat): if self.modifier == "-": return fstat[stat.ST_MTIME] >= self.mtime else: return fstat[stat.ST_MTIME] <= self.mtime class GrepOption(Option): """Match files when a pattern occurs within the file. The option name is 'grep', e.g. {'grep' : '(foo)|(bar}'}. """ def __init__(self, key, value): try: self.regex = re.compile(value) except re.error: raise ValueError('invalid regular expression: "{}"'.format(value)) def requires(self): return _REQUIRES_CONTENTS | _REQUIRES_STAT def match(self, dirname, filename, fstat): if not stat.S_ISREG(fstat[stat.ST_MODE]): return None dfilename = os.path.join(dirname, filename) with BufferedReader(dfilename, mode="rb") as bread: for chunk in bread: if self.regex.search(chunk): return dfilename return None class PrintOption(Option): """ Return information about a matched file. Print options are specified as a comma and/or space separated list of one or more of the following: group = group name md5 = MD5 digest of file contents mode = file mode (as integer) mtime = last modification time (as time_t) name = file basename path = file absolute path size = file size in bytes type = file type user = user name """ def __init__(self, key, value): self.need_stat = False self.print_title = False self.fmt = [] for arg in value.replace(",", " ").split(): self.fmt.append(arg) if arg not in ["name", "path"]: self.need_stat = True if not self.fmt: self.fmt.append("path") def requires(self): return _REQUIRES_STAT if self.need_stat else _REQUIRES_PATH def execute(self, fullpath, fstat, test=False): result = [] for arg in self.fmt: if arg == "path": result.append(fullpath) elif arg == "name": result.append(os.path.basename(fullpath)) elif arg == "size": result.append(fstat[stat.ST_SIZE]) elif arg == "type": result.append(_FILE_TYPES.get(stat.S_IFMT(fstat[stat.ST_MODE]), "?")) elif arg == "mode": # PY3 compatibility: Use radix value 8 on int type-cast explicitly result.append(int(oct(fstat[stat.ST_MODE])[-3:], 8)) elif arg == "mtime": result.append(fstat[stat.ST_MTIME]) elif arg == "user": uid = fstat[stat.ST_UID] try: result.append(pwd.getpwuid(uid).pw_name) except KeyError: result.append(uid) elif arg == "group": gid = fstat[stat.ST_GID] try: result.append(grp.getgrgid(gid).gr_name) except KeyError: result.append(gid) elif arg == "md5": if stat.S_ISREG(fstat[stat.ST_MODE]): md5digest = salt.utils.hashutils.get_hash(fullpath, "md5") result.append(md5digest) else: result.append("") if len(result) == 1: return result[0] else: return result class DeleteOption(TypeOption): """ Deletes matched file. Delete options are one or more of the following: a: all file types b: block device c: character device d: directory p: FIFO (named pipe) f: plain file l: symlink s: socket """ def __init__(self, key, value): if "a" in value: value = "bcdpfls" super().__init__(key, value) def execute(self, fullpath, fstat, test=False): if test: return fullpath try: if os.path.isfile(fullpath) or os.path.islink(fullpath): os.remove(fullpath) elif os.path.isdir(fullpath): shutil.rmtree(fullpath) except OSError as exc: return None return fullpath class ExecOption(Option): """ Execute the given command, {} replaced by filename. Quote the {} if commands might include whitespace. """ def __init__(self, key, value): self.command = value def execute(self, fullpath, fstat, test=False): try: command = self.command.replace("{}", fullpath) print(salt.utils.args.shlex_split(command)) p = Popen(salt.utils.args.shlex_split(command), stdout=PIPE, stderr=PIPE) (out, err) = p.communicate() if err: log.error( "Error running command: %s\n\n%s", command, salt.utils.stringutils.to_str(err), ) return "{}:\n{}\n".format(command, salt.utils.stringutils.to_str(out)) except Exception as e: # pylint: disable=broad-except log.error('Exception while executing command "%s":\n\n%s', command, e) return "{}: Failed".format(fullpath) class Finder: def __init__(self, options): self.actions = [] self.maxdepth = None self.mindepth = 0 self.test = False criteria = { _REQUIRES_PATH: list(), _REQUIRES_STAT: list(), _REQUIRES_CONTENTS: list(), } if "mindepth" in options: self.mindepth = options["mindepth"] del options["mindepth"] if "maxdepth" in options: self.maxdepth = options["maxdepth"] del options["maxdepth"] if "test" in options: self.test = options["test"] del options["test"] for key, value in options.items(): if key.startswith("_"): # this is a passthrough object, continue continue if not value: raise ValueError('missing value for "{}" option'.format(key)) try: obj = globals()[key.title() + "Option"](key, value) except KeyError: raise ValueError('invalid option "{}"'.format(key)) if hasattr(obj, "match"): requires = obj.requires() if requires & _REQUIRES_CONTENTS: criteria[_REQUIRES_CONTENTS].append(obj) elif requires & _REQUIRES_STAT: criteria[_REQUIRES_STAT].append(obj) else: criteria[_REQUIRES_PATH].append(obj) if hasattr(obj, "execute"): self.actions.append(obj) if not self.actions: self.actions.append(PrintOption("print", "")) # order criteria so that least expensive checks are done first self.criteria = ( criteria[_REQUIRES_PATH] + criteria[_REQUIRES_STAT] + criteria[_REQUIRES_CONTENTS] ) def find(self, path): """ Generate filenames in path that satisfy criteria specified in the constructor. This method is a generator and should be repeatedly called until there are no more results. """ if self.mindepth < 1: dirpath, name = os.path.split(path) match, fstat = self._check_criteria(dirpath, name, path) if match: yield from self._perform_actions(path, fstat=fstat) for dirpath, dirs, files in salt.utils.path.os_walk(path): relpath = os.path.relpath(dirpath, path) depth = path_depth(relpath) + 1 if depth >= self.mindepth and ( self.maxdepth is None or self.maxdepth >= depth ): for name in dirs + files: fullpath = os.path.join(dirpath, name) match, fstat = self._check_criteria(dirpath, name, fullpath) if match: yield from self._perform_actions(fullpath, fstat=fstat) if self.maxdepth is not None and depth > self.maxdepth: dirs[:] = [] def _check_criteria(self, dirpath, name, fullpath, fstat=None): match = True for criterion in self.criteria: if fstat is None and criterion.requires() & _REQUIRES_STAT: try: fstat = os.stat(fullpath) except OSError: fstat = os.lstat(fullpath) if not criterion.match(dirpath, name, fstat): match = False break return match, fstat def _perform_actions(self, fullpath, fstat=None): for action in self.actions: if fstat is None and action.requires() & _REQUIRES_STAT: try: fstat = os.stat(fullpath) except OSError: fstat = os.lstat(fullpath) result = action.execute(fullpath, fstat, test=self.test) if result is not None: yield result def path_depth(path): depth = 0 head = path while True: head, tail = os.path.split(head) if not tail and (not head or head in _PATH_DEPTH_IGNORED): break if tail and tail not in _PATH_DEPTH_IGNORED: depth += 1 return depth def find(path, options): """ WRITEME """ finder = Finder(options) for path in finder.find(path): yield path def _main(): if len(sys.argv) < 2: sys.stderr.write("usage: {} path [options]\n".format(sys.argv[0])) sys.exit(salt.defaults.exitcodes.EX_USAGE) path = sys.argv[1] criteria = {} for arg in sys.argv[2:]: key, value = arg.split("=") criteria[key] = value try: finder = Finder(criteria) except ValueError as ex: sys.stderr.write("error: {}\n".format(ex)) sys.exit(salt.defaults.exitcodes.EX_GENERIC) for result in finder.find(path): print(result) if __name__ == "__main__": _main()
Save