Codebase list dtrx / 11470c6
Import Upstream version 6.5 Dmitry Bogatov 5 years ago
9 changed file(s) with 309 addition(s) and 138 deletion(s). Raw diff Collapse all Expand all
2727 rpm2cpio, cpio
2828
2929 deb archives
30 ar, tar, zcat
30 ar, tar, zcat, bzcat, lzcat
3131
3232 gem archives
3333 tar, zcat
00 Changes in dtrx
11 ===============
2
3 Version 6.5
4 -----------
5
6 Enhancements
7 ~~~~~~~~~~~~
8
9 * When you list archive contents with -l or -t, dtrx will start printing
10 results much faster than it used to. There's a small chance that it
11 will print some incorrect listings if it misdetects the archive type of
12 a given file, but it will show you an error message when that happens.
13
14 * dtrx recognizes more kinds of compressed tar archives by their
15 extension.
16
17 * You can now extract newer .deb packages that are compressed with bzip2
18 or lzma.
19
20 Bug fixes
21 ~~~~~~~~~
22
23 * When extracting an archive that contained a file with a mismatched
24 filename, the prompt would offer you a chance to "rename the directory"
25 instead of "rename the file." This wording has been fixed, along with
26 some other wording adjustments in the prompts generally.
27
28 * Perform more reliable detection of the terminal size, and improve word
29 wrapping on prompts.
30
31 Other changes
32 ~~~~~~~~~~~~~
33
34 * The README is now written like a man page, and can be converted to a man
35 page by using rst2man_.
36
37 .. _rst2man: http://docutils.sourceforge.net/sandbox/manpage-writer/
238
339 Version 6.4
440 -----------
00 Metadata-Version: 1.0
11 Name: dtrx
2 Version: 6.4
2 Version: 6.5
33 Summary: Script to intelligently extract multiple archive types
44 Home-page: http://www.brettcsmith.org/2007/dtrx/
55 Author: Brett Smith
66 Author-email: brettcsmith@brettcsmith.org
77 License: GNU General Public License, version 3 or later
8 Description: UNKNOWN
8 Download-URL: http://www.brettcsmith.org/2007/dtrx/
9 Description: dtrx extracts archives in a number of different
10 formats; it currently supports tar, zip (including self-extracting
11 .exe files), cpio, rpm, deb, gem, 7z, cab, rar, and InstallShield
12 files. It can also decompress files compressed with gzip, bzip2,
13 lzma, or compress.
14
15 In addition to providing one command to handle many different archive
16 types, dtrx also aids the user by extracting contents consistently.
17 By default, everything will be written to a dedicated directory
18 that's named after the archive. dtrx will also change the
19 permissions to ensure that the owner can read and write all those
20 files.
921 Platform: UNKNOWN
22 Classifier: Development Status :: 5 - Production/Stable
23 Classifier: Environment :: Console
24 Classifier: Intended Audience :: End Users/Desktop
25 Classifier: Intended Audience :: System Administrators
26 Classifier: License :: OSI Approved :: GNU General Public License (GPL)
27 Classifier: Natural Language :: English
28 Classifier: Operating System :: POSIX
29 Classifier: Programming Language :: Python
30 Classifier: Topic :: Utilities
0 dtrx - Intelligent archive extraction
1 =====================================
0 ====
1 dtrx
2 ====
23
3 Introduction
4 ------------
4 ----------------------------------
5 cleanly extract many archive types
6 ----------------------------------
7
8 :Author: Brett Smith <brettcsmith@brettcsmith.org>
9 :Date: 2009-07-04
10 :Copyright:
11
12 dtrx 6.5 is copyright © 2006-2009 Brett Smith and others. Feel free to
13 send comments, bug reports, patches, and so on. You can find the latest
14 version of dtrx on its home page at
15 <http://www.brettcsmith.org/2007/dtrx/>.
16
17 dtrx is free software; you can redistribute it and/or modify it under the
18 terms of the GNU General Public License as published by the Free Software
19 Foundation; either version 3 of the License, or (at your option) any
20 later version.
21
22 This program is distributed in the hope that it will be useful, but
23 WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
25 Public License for more details.
26
27 You should have received a copy of the GNU General Public License along
28 with this program; if not, see <http://www.gnu.org/licenses/>.
29
30 :Version: 6.5
31 :Manual section: 1
32
33 SYNOPSIS
34 ========
35
36 dtrx [OPTIONS] ARCHIVE [ARCHIVE ...]
37
38 DESCRIPTION
39 ===========
540
641 dtrx extracts archives in a number of different formats; it currently
7 supports tar, zip, cpio, rpm, deb, gem, 7z, cab, and rar files. It can
8 also decompress files compressed with gzip, bzip2, lzma, or compress.
42 supports tar, zip (including self-extracting .exe files), cpio, rpm, deb,
43 gem, 7z, cab, rar, and InstallShield files. It can also decompress files
44 compressed with gzip, bzip2, lzma, or compress.
945
1046 In addition to providing one command to handle many different archive
1147 types, dtrx also aids the user by extracting contents consistently. By
1349 after the archive. dtrx will also change the permissions to ensure that the
1450 owner can read and write all those files.
1551
16 Running dtrx
17 ------------
18
1952 To run dtrx, simply call it with the archive(s) you wish to extract as
2053 arguments. For example::
2154
2255 $ dtrx coreutils-5.*.tar.gz
56
57 OPTIONS
58 =======
2359
2460 dtrx supports a number of options to mandate specific behavior:
2561
83119
84120 --version
85121 Display dtrx's version, copyright, and license information.
86
87 Other Useful Information
88 ------------------------
89
90 dtrx 6.4 is copyright ⓒ 2006, 2007, 2008 `Brett Smith`_ and others. Feel
91 free to send comments, bug reports, patches, and so on. You can find the
92 latest version of dtrx on `its home page`_.
93
94 .. _`Brett Smith`: mailto:brettcsmith@brettcsmith.org
95 .. _`its home page`: http://www.brettcsmith.org/2007/dtrx/
96
97 dtrx is free software; you can redistribute it and/or modify it under the
98 terms of the GNU General Public License as published by the Free Software
99 Foundation; either version 3 of the License, or (at your option) any later
100 version.
101
102 This program is distributed in the hope that it will be useful, but WITHOUT
103 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
104 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
105 more details.
106
107 You should have received a copy of the GNU General Public License along
108 with this program; if not, see <http://www.gnu.org/licenses/>.
11 # -*- coding: utf-8 -*-
22 #
33 # dtrx -- Intelligently extract various archive types.
4 # Copyright ⓒ 2006, 2007, 2008 Brett Smith <brettcsmith@brettcsmith.org>
5 # Copyright ⓒ 2008 Peter Kelemen <Peter.Kelemen@gmail.com>
4 # Copyright © 2006-2009 Brett Smith <brettcsmith@brettcsmith.org>
5 # Copyright © 2008 Peter Kelemen <Peter.Kelemen@gmail.com>
66 #
77 # This program is free software; you can redistribute it and/or modify it
88 # under the terms of the GNU General Public License as published by the
2020 # Python 2.3 string methods: 'rfind', 'rindex', 'rjust', 'rstrip'
2121
2222 import errno
23 import fcntl
2324 import logging
2425 import mimetypes
2526 import optparse
2829 import shutil
2930 import signal
3031 import stat
32 import string
33 import struct
3134 import subprocess
3235 import sys
3336 import tempfile
37 import termios
3438 import textwrap
3539 import traceback
3640
3943 except NameError:
4044 from sets import Set as set
4145
42 VERSION = "6.4"
46 VERSION = "6.5"
4347 VERSION_BANNER = """dtrx version %s
44 Copyright ⓒ 2006, 2007, 2008 Brett Smith <brettcsmith@brettcsmith.org>
45 Copyright ⓒ 2008 Peter Kelemen <Peter.Kelemen@gmail.com>
48 Copyright © 2006-2009 Brett Smith <brettcsmith@brettcsmith.org>
49 Copyright © 2008 Peter Kelemen <Peter.Kelemen@gmail.com>
4650
4751 This program is free software; you can redistribute it and/or modify it
4852 under the terms of the GNU General Public License as published by the
167171 return index
168172 return None
169173
174 def add_process(self, processes, command, stdin, stdout):
175 try:
176 processes.append(subprocess.Popen(command, stdin=stdin,
177 stdout=stdout,
178 stderr=self.stderr))
179 except OSError, error:
180 if error.errno == errno.ENOENT:
181 raise ExtractorUnusable("could not run %s" % (command[0],))
182 raise
183
170184 def run_pipes(self, final_stdout=None):
171185 if not self.pipes:
172186 return
173187 elif final_stdout is None:
174 # FIXME: Buffering this might be dumb.
175 final_stdout = tempfile.TemporaryFile()
188 final_stdout = open('/dev/null', 'w')
176189 num_pipes = len(self.pipes)
177190 last_pipe = num_pipes - 1
178191 processes = []
185198 stdout = final_stdout
186199 else:
187200 stdout = subprocess.PIPE
188 try:
189 processes.append(subprocess.Popen(command, stdin=stdin,
190 stdout=stdout,
191 stderr=self.stderr))
192 except OSError, error:
193 if error.errno == errno.ENOENT:
194 raise ExtractorUnusable("could not run %s" % (command[0],))
195 raise
201 self.add_process(processes, command, stdin, stdout)
196202 self.exit_codes = [pipe.wait() for pipe in processes]
197203 self.archive.close()
198204 for index in range(last_pipe):
284290 self.archive.close()
285291 os.chdir(old_path)
286292
287 def get_filenames(self):
288 self.pipe(self.list_pipe, "listing")
289 self.run_pipes()
293 def get_filenames(self, internal=False):
294 if not internal:
295 self.pipe(self.list_pipe, "listing")
296 processes = []
297 stdin = self.archive
298 for command in [pipe[0] for pipe in self.pipes]:
299 self.add_process(processes, command, stdin, subprocess.PIPE)
300 stdin = processes[-1].stdout
301 get_output_line = processes[-1].stdout.readline
302 while True:
303 line = get_output_line()
304 if not line:
305 break
306 yield line.rstrip('\n')
307 self.exit_codes = [pipe.wait() for pipe in processes]
308 self.archive.close()
309 for process in processes:
310 process.stdout.close()
290311 self.check_success(False)
291 self.archive.seek(0, 0)
292 while True:
293 line = self.archive.readline()
294 if not line:
295 self.archive.close()
296 return
297 yield line.rstrip('\n')
298312
299313
300314 class CompressionExtractor(BaseExtractor):
376390
377391 class DebExtractor(TarExtractor):
378392 file_type = 'Debian package'
393 data_re = re.compile(r'^data\.tar\.[a-z0-9]+$')
379394
380395 def prepare(self):
381 self.pipe(['ar', 'p', self.filename, 'data.tar.gz'],
382 "data.tar.gz extraction")
383 self.pipe(['zcat'], "data.tar.gz decompression")
396 self.pipe(['ar', 't', self.filename], "finding package data file")
397 for filename in self.get_filenames(internal=True):
398 if self.data_re.match(filename):
399 data_filename = filename
400 break
401 else:
402 raise ExtractorError(".deb contains no data.tar file")
403 self.archive.seek(0, 0)
404 self.pipes.pop()
405 # self.pipes = start_pipes
406 encoding = mimetypes.guess_type(data_filename)[1]
407 if not encoding:
408 raise ExtractorError("data.tar file has unrecognized encoding")
409 self.pipe(['ar', 'p', self.filename, data_filename],
410 "extracting data.tar from .deb")
411 self.pipe([self.decoders[encoding]], "decoding data.tar")
384412
385413 def basename(self):
386414 pieces = os.path.basename(self.filename).split('_')
470498 if fn_index is not None:
471499 break
472500 else:
473 fn_index = line.rindex(' ') + 1
501 fn_index = string.rindex(line, ' ') + 1
474502 elif fn_index is not None:
475503 yield line[fn_index:]
476504 self.archive.close()
660688
661689 class BasePolicy(object):
662690 try:
663 width = int(os.environ['COLUMNS'])
664 except (KeyError, ValueError):
691 size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ,
692 struct.pack("HHHH", 0, 0, 0, 0))
693 width = struct.unpack("HHHH", size)[1]
694 except IOError:
665695 width = 80
666 wrapper = textwrap.TextWrapper(width=width - 1)
667
696 width = width - 1
697 choice_wrapper = textwrap.TextWrapper(width=width, initial_indent=' * ',
698 subsequent_indent=' ',
699 break_long_words=False)
700
668701 def __init__(self, options):
669702 self.current_policy = None
670703 if options.batch:
672705 else:
673706 self.permanent_policy = None
674707
675 def wrap(self, question, filename):
676 # Note: This function assumes the filename is the first thing in the
677 # question text, and that's the only place it appears.
678 if len(self.wrapper.wrap(filename + ' a')) > 1:
679 return [filename] + self.wrapper.wrap(question[3:])
680 return self.wrapper.wrap(question % (filename,))
681
682708 def ask_question(self, question):
683 question = question + self.choices
709 question = question + ["You can:"]
710 for choice in self.choices:
711 question.extend(self.choice_wrapper.wrap(choice))
684712 while True:
685713 print "\n".join(question)
686714 try:
692720 except KeyError:
693721 print
694722
723 def wrap(self, question, *args):
724 words = question.split()
725 for arg in args:
726 words[words.index('%s')] = arg
727 result = [words.pop(0)]
728 for word in words:
729 extend = '%s %s' % (result[-1], word)
730 if len(extend) > self.width:
731 result.append(word)
732 else:
733 result[-1] = extend
734 return result
735
695736 def __cmp__(self, other):
696737 return cmp(self.current_policy, other)
697738
699740 class OneEntryPolicy(BasePolicy):
700741 answers = {'h': EXTRACT_HERE, 'i': EXTRACT_WRAP, 'r': EXTRACT_RENAME,
701742 '': EXTRACT_WRAP}
702 choices = ["You can:",
703 " * extract it Inside another directory",
704 " * extract it and Rename the directory",
705 " * extract it Here"]
743 choice_template = ["extract the %s _I_nside a new directory named %s",
744 "extract the %s and _R_ename it %s",
745 "extract the %s _H_ere"]
706746 prompt = "What do you want to do? (I/r/h) "
707747
708748 def __init__(self, options):
723763 raise ValueError("bad value %s for default policy" % (default,))
724764
725765 def prep(self, archive_filename, extractor):
726 question = self.wrap(("%%s contains one %s, but its name " +
727 "doesn't match.") %
728 (extractor.content_type,), archive_filename)
766 question = self.wrap(
767 "%s contains one %s but its name doesn't match.",
768 archive_filename, extractor.content_type)
729769 question.append(" Expected: " + extractor.basename())
730770 question.append(" Actual: " + extractor.content_name)
771 choice_vars = (extractor.content_type, extractor.basename())
772 self.choices = [text % choice_vars[:text.count('%s')]
773 for text in self.choice_template]
731774 self.current_policy = (self.permanent_policy or
732775 self.ask_question(question))
733776
738781 class RecursionPolicy(BasePolicy):
739782 answers = {'o': RECURSE_ONCE, 'a': RECURSE_ALWAYS, 'n': RECURSE_NOT_NOW,
740783 'v': RECURSE_NEVER, 'l': RECURSE_LIST, '': RECURSE_NOT_NOW}
741 choices = ["You can:",
742 " * Always extract included archives",
743 " * extract included archives this Once",
744 " * choose Not to extract included archives",
745 " * neVer extract included archives",
746 " * List included archives"]
784 choices = ["_A_lways extract included archives during this session",
785 "extract included archives this _O_nce",
786 "choose _N_ot to extract included archives this once",
787 "ne_V_er extract included archives during this session",
788 "_L_ist included archives"]
747789 prompt = "What do you want to do? (a/o/N/v/l) "
748790
749791 def __init__(self, options):
758800 if (self.permanent_policy is not None) or (archive_count == 0):
759801 self.current_policy = self.permanent_policy or RECURSE_NOT_NOW
760802 return
761 question = self.wrap(("%%s contains %s other archive file(s), " +
762 "out of %s file(s) total.") %
763 (archive_count, extractor.file_count),
764 current_filename)
803 question = self.wrap(
804 "%s contains %s other archive file(s), out of %s file(s) total.",
805 current_filename, archive_count, extractor.file_count)
765806 if target == '.':
766807 target = ''
767808 included_root = extractor.included_root
839880 for extension in ext_info.get('extensions', ()):
840881 extension_map.setdefault(extension, []).append((ext_name, None))
841882
842 for mapping in (('tar', 'bzip2', 'tar.bz2'),
883 for mapping in (('tar', 'bzip2', 'tar.bz2', 'tbz2', 'tb2', 'tbz'),
843884 ('tar', 'gzip', 'tar.gz', 'tgz'),
885 ('tar', 'lzma', 'tar.lzma', 'tlz'),
886 ('tar', 'compress', 'tar.Z', 'taz'),
844887 ('compress', 'gzip', 'Z', 'gz'),
845888 ('compress', 'bzip2', 'bz2'),
846889 ('compress', 'lzma', 'lzma')):
935978 self.options = options
936979 self.filenames = filenames
937980 self.target = None
981 self.do_print = False
938982
939983 def report(self, function, *args):
940984 try:
944988 logger.debug(''.join(traceback.format_exception(*sys.exc_info())))
945989 return error
946990
991 def show_filename(self, filename):
992 if len(self.filenames) < 2:
993 return
994 elif self.do_print:
995 print
996 else:
997 self.do_print = True
998 print "%s:" % (filename,)
999
9471000
9481001 class ExtractionAction(BaseAction):
9491002 handlers = [FlatHandler, OverwriteHandler, MatchHandler, EmptyHandler,
9501003 BombHandler]
951
952 def __init__(self, options, filenames):
953 BaseAction.__init__(self, options, filenames)
954 self.did_print = False
9551004
9561005 def get_handler(self, extractor):
9571006 if extractor.content_type in ONE_ENTRY_UNKNOWN:
9661015 def show_extraction(self, extractor):
9671016 if self.options.log_level > logging.INFO:
9681017 return
969 elif self.did_print:
970 print
971 else:
972 self.did_print = True
973 print "%s:" % (self.current_filename,)
1018 self.show_filename(self.current_filename)
9741019 if extractor.contents is None:
9751020 print self.current_handler.target
9761021 return
10061051
10071052
10081053 class ListAction(BaseAction):
1009 def __init__(self, options, filenames):
1010 BaseAction.__init__(self, options, filenames)
1011 self.count = 0
1012
1013 def get_list(self, extractor):
1014 # Note: The reason I'm getting all the filenames up front is
1015 # because if we run into trouble partway through the archive, we'll
1016 # try another extractor. So before we display anything we have to
1017 # be sure this one is successful. We maybe don't have to be quite
1018 # this conservative but this is the easy way out for now.
1019 self.filelist = list(extractor.get_filenames())
1020
1021 def show_list(self, filename):
1022 self.count += 1
1023 if len(self.filenames) != 1:
1024 if self.count > 1:
1025 print
1026 print "%s:" % (filename,)
1027 print '\n'.join(self.filelist)
1028
1054 def list_filenames(self, extractor, filename):
1055 # We get a line first to make sure there's not going to be some
1056 # basic error before we show what filename we're listing.
1057 filename_lister = extractor.get_filenames()
1058 try:
1059 first_line = filename_lister.next()
1060 except StopIteration:
1061 self.show_filename(filename)
1062 else:
1063 self.did_list = True
1064 self.show_filename(filename)
1065 print first_line
1066 for line in filename_lister:
1067 print line
1068
10291069 def run(self, filename, extractor):
1030 return (self.report(self.get_list, extractor) or
1031 self.report(self.show_list, filename))
1070 self.did_list = False
1071 error = self.report(self.list_filenames, extractor, filename)
1072 if error and self.did_list:
1073 logger.error("lister failed: ignore above listing for %s" %
1074 (filename,))
1075 return error
10321076
10331077
10341078 class ExtractorApplication(object):
22 from distutils.core import setup
33
44 setup(name="dtrx",
5 version = "6.4",
5 version = "6.5",
66 description = "Script to intelligently extract multiple archive types",
77 author = "Brett Smith",
88 author_email = "brettcsmith@brettcsmith.org",
99 url = "http://www.brettcsmith.org/2007/dtrx/",
10 download_url = "http://www.brettcsmith.org/2007/dtrx/",
1011 scripts = ['scripts/dtrx'],
11 license = "GNU General Public License, version 3 or later"
12 )
12 license = "GNU General Public License, version 3 or later",
13 classifiers = ['Development Status :: 5 - Production/Stable',
14 'Environment :: Console',
15 'Intended Audience :: End Users/Desktop',
16 'Intended Audience :: System Administrators',
17 'License :: OSI Approved :: GNU General Public License (GPL)',
18 'Natural Language :: English',
19 'Operating System :: POSIX',
20 'Programming Language :: Python',
21 'Topic :: Utilities'],
22 long_description = """dtrx extracts archives in a number of different
23 formats; it currently supports tar, zip (including self-extracting
24 .exe files), cpio, rpm, deb, gem, 7z, cab, rar, and InstallShield
25 files. It can also decompress files compressed with gzip, bzip2,
26 lzma, or compress.
27
28 In addition to providing one command to handle many different archive
29 types, dtrx also aids the user by extracting contents consistently.
30 By default, everything will be written to a dedicated directory
31 that's named after the archive. dtrx will also change the
32 permissions to ensure that the owner can read and write all those
33 files."""
34 )
00 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
12 #
23 # compare.py -- High-level tests for dtrx.
3 # Copyright (c) 2006, 2007, 2008 Brett Smith <brettcsmith@brettcsmith.org>.
4 # Copyright © 2006-2009 Brett Smith <brettcsmith@brettcsmith.org>.
45 #
56 # This program is free software; you can redistribute it and/or modify it
67 # under the terms of the GNU General Public License as published by the
5354 setattr(self, 'options', kwargs.get('options', '-n').split())
5455 setattr(self, 'filenames', kwargs.get('filenames', '').split())
5556 for key in ('directory', 'prerun', 'posttest', 'baseline', 'error',
56 'grep', 'antigrep', 'input', 'output', 'cleanup'):
57 'input', 'output', 'cleanup'):
5758 setattr(self, key, kwargs.get(key, None))
59 for key in ('grep', 'antigrep'):
60 value = kwargs.get(key, [])
61 if isinstance(value, str):
62 value = [value]
63 setattr(self, key, value)
5864
5965 def get_results(self, commands, stdin=None):
6066 print >>output_buffer, "Output from %s:" % (' '.join(commands),)
164170 return None
165171
166172 def grep_output(self, output):
167 if self.grep and (not re.search(self.grep.replace(' ', '\\s+'),
168 output, re.MULTILINE)):
169 return "output did not match %s" % (self.grep)
170 elif self.antigrep and re.search(self.antigrep.replace(' ', '\\s+'),
171 output, re.MULTILINE):
172 return "output matched antigrep %s" % (self.antigrep)
173 for pattern in self.grep:
174 if not re.search(pattern.replace(' ', '\\s+'), output,
175 re.MULTILINE):
176 return "output did not match %s" % (pattern)
177 for pattern in self.antigrep:
178 if re.search(pattern.replace(' ', '\\s+'), output, re.MULTILINE):
179 return "output matched antigrep %s" % (self.antigrep)
173180 return None
174181
175182 def check_output(self, output):
Binary diff not shown
2727 mkdir test-1.23
2828 cd test-1.23
2929 ar p ../$1 data.tar.gz | tar -zx
30
31 - name: .deb with LZMA compression
32 filenames: test-2_all.deb
33 baseline: |
34 mkdir test-2
35 cd test-2
36 ar p ../$1 data.tar.lzma | lzcat | tar -x
3037
3138 - name: basic .gem
3239 filenames: test-1.23.gem
338345 cd test-onefile
339346 tar -zxf ../$1
340347
348 - name: prompt wording with one file
349 options: ""
350 filenames: test-onefile.tar.gz
351 input: i
352 grep: file _I_nside
353
341354 - name: one file extracted with rename, with Expected text
342355 options: ""
343356 filenames: test-onefile.tar.gz
357370 - name: bomb with preceding dot in the table
358371 filenames: test-dot-first-bomb.tar.gz
359372 options: ""
360 antigrep: one entry
373 antigrep: one
361374 baseline: |
362375 mkdir test-dot-first-bomb
363376 cd test-dot-first-bomb
511524 grep: "^1/2/3$"
512525 antigrep: "^dtrx:"
513526
527 - name: listing multiple file with misleading extensions
528 options: -l
529 filenames: trickery.tar.gz trickery.tar.gz
530 prerun: cp ${1}test-1.23.zip ${1}trickery.tar.gz
531 cleanup: rm -f ${1}trickery.tar.gz
532 output: |
533 trickery.tar.gz:
534 1/2/3
535 a/b
536 foobar
537
538 trickery.tar.gz:
539 1/2/3
540 a/b
541 foobar
542
514543 - name: non-archive error
515544 filenames: /dev/null
516545 error: true
605634 directory: busydir
606635 filenames: ../test-onedir.tar.gz
607636 output: |
608 ../test-onedir.tar.gz:
609637 test/
610638 test/foobar
611639 test/quux