Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d27b455

Browse files
Issue #13477: Added command line interface to the tarfile module.
Original patch by Berker Peksag.
1 parent 44e2eaa commit d27b455

4 files changed

Lines changed: 323 additions & 1 deletion

File tree

Doc/library/tarfile.rst

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,67 @@ A :class:`TarInfo` object also provides some convenient query methods:
591591
Return :const:`True` if it is one of character device, block device or FIFO.
592592

593593

594+
.. _tarfile-commandline:
595+
596+
Command Line Interface
597+
----------------------
598+
599+
.. versionadded:: 3.4
600+
601+
The :mod:`tarfile` module provides a simple command line interface to interact
602+
with tar archives.
603+
604+
If you want to create a new tar archive, specify its name after the :option:`-c`
605+
option and then list the filename(s) that should be included::
606+
607+
$ python -m tarfile -c monty.tar spam.txt eggs.txt
608+
609+
Passing a directory is also acceptable::
610+
611+
$ python -m tarfile -c monty.tar life-of-brian_1979/
612+
613+
If you want to extract a tar archive into the current directory, use
614+
the :option:`-e` option::
615+
616+
$ python -m tarfile -e monty.tar
617+
618+
You can also extract a tar archive into a different directory by passing the
619+
directory's name::
620+
621+
$ python -m tarfile -e monty.tar other-dir/
622+
623+
For a list of the files in a tar archive, use the :option:`-l` option::
624+
625+
$ python -m tarfile -l monty.tar
626+
627+
628+
Command line options
629+
~~~~~~~~~~~~~~~~~~~~
630+
631+
.. cmdoption:: -l <tarfile>
632+
--list <tarfile>
633+
634+
List files in a tarfile.
635+
636+
.. cmdoption:: -c <tarfile> <source1> <sourceN>
637+
--create <tarfile> <source1> <sourceN>
638+
639+
Create tarfile from source files.
640+
641+
.. cmdoption:: -e <tarfile> [<output_dir>]
642+
--extract <tarfile> [<output_dir>]
643+
644+
Extract tarfile into the current directory if *output_dir* is not specified.
645+
646+
.. cmdoption:: -t <tarfile>
647+
--test <tarfile>
648+
649+
Test whether the tarfile is valid or not.
650+
651+
.. cmdoption:: -v, --verbose
652+
653+
Verbose output
654+
594655
.. _tar-examples:
595656

596657
Examples

Lib/tarfile.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2404,3 +2404,97 @@ def is_tarfile(name):
24042404

24052405
bltn_open = open
24062406
open = TarFile.open
2407+
2408+
2409+
def main():
2410+
import argparse
2411+
2412+
description = 'A simple command line interface for tarfile module.'
2413+
parser = argparse.ArgumentParser(description=description)
2414+
parser.add_argument('-v', '--verbose', action='store_true', default=False,
2415+
help='Verbose output')
2416+
group = parser.add_mutually_exclusive_group()
2417+
group.add_argument('-l', '--list', metavar='<tarfile>',
2418+
help='Show listing of a tarfile')
2419+
group.add_argument('-e', '--extract', nargs='+',
2420+
metavar=('<tarfile>', '<output_dir>'),
2421+
help='Extract tarfile into target dir')
2422+
group.add_argument('-c', '--create', nargs='+',
2423+
metavar=('<name>', '<file>'),
2424+
help='Create tarfile from sources')
2425+
group.add_argument('-t', '--test', metavar='<tarfile>',
2426+
help='Test if a tarfile is valid')
2427+
args = parser.parse_args()
2428+
2429+
if args.test:
2430+
src = args.test
2431+
if is_tarfile(src):
2432+
with open(src, 'r') as tar:
2433+
tar.getmembers()
2434+
print(tar.getmembers(), file=sys.stderr)
2435+
if args.verbose:
2436+
print('{!r} is a tar archive.'.format(src))
2437+
else:
2438+
parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
2439+
2440+
elif args.list:
2441+
src = args.list
2442+
if is_tarfile(src):
2443+
with TarFile.open(src, 'r:*') as tf:
2444+
tf.list(verbose=args.verbose)
2445+
else:
2446+
parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
2447+
2448+
elif args.extract:
2449+
if len(args.extract) == 1:
2450+
src = args.extract[0]
2451+
curdir = os.curdir
2452+
elif len(args.extract) == 2:
2453+
src, curdir = args.extract
2454+
else:
2455+
parser.exit(1, parser.format_help())
2456+
2457+
if is_tarfile(src):
2458+
with TarFile.open(src, 'r:*') as tf:
2459+
tf.extractall(path=curdir)
2460+
if args.verbose:
2461+
if curdir == '.':
2462+
msg = '{!r} file is extracted.'.format(src)
2463+
else:
2464+
msg = ('{!r} file is extracted '
2465+
'into {!r} directory.').format(src, curdir)
2466+
print(msg)
2467+
else:
2468+
parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
2469+
2470+
elif args.create:
2471+
tar_name = args.create.pop(0)
2472+
_, ext = os.path.splitext(tar_name)
2473+
compressions = {
2474+
# gz
2475+
'gz': 'gz',
2476+
'tgz': 'gz',
2477+
# xz
2478+
'xz': 'xz',
2479+
'txz': 'xz',
2480+
# bz2
2481+
'bz2': 'bz2',
2482+
'tbz': 'bz2',
2483+
'tbz2': 'bz2',
2484+
'tb2': 'bz2',
2485+
}
2486+
tar_mode = 'w:' + compressions[ext] if ext in compressions else 'w'
2487+
tar_files = args.create
2488+
2489+
with TarFile.open(tar_name, tar_mode) as tf:
2490+
for file_name in tar_files:
2491+
tf.add(file_name)
2492+
2493+
if args.verbose:
2494+
print('{!r} file created.'.format(tar_name))
2495+
2496+
else:
2497+
parser.exit(1, parser.format_help())
2498+
2499+
if __name__ == '__main__':
2500+
main()

Lib/test/test_tarfile.py

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import unittest
88
import tarfile
99

10-
from test import support
10+
from test import support, script_helper
1111

1212
# Check for our compression modules.
1313
try:
@@ -27,11 +27,13 @@ def md5sum(data):
2727
return md5(data).hexdigest()
2828

2929
TEMPDIR = os.path.abspath(support.TESTFN) + "-tardir"
30+
tarextdir = TEMPDIR + '-extract-test'
3031
tarname = support.findfile("testtar.tar")
3132
gzipname = os.path.join(TEMPDIR, "testtar.tar.gz")
3233
bz2name = os.path.join(TEMPDIR, "testtar.tar.bz2")
3334
xzname = os.path.join(TEMPDIR, "testtar.tar.xz")
3435
tmpname = os.path.join(TEMPDIR, "tmp.tar")
36+
dotlessname = os.path.join(TEMPDIR, "testtar")
3537

3638
md5_regtype = "65f477c818ad9e15f7feab0c6d37742f"
3739
md5_sparse = "a54fbc4ca4f4399a90e1b27164012fc6"
@@ -1724,6 +1726,168 @@ def test_number_field_limits(self):
17241726
tarfile.itn(0x10000000000, 6, tarfile.GNU_FORMAT)
17251727

17261728

1729+
class CommandLineTest(unittest.TestCase):
1730+
1731+
def tarfilecmd(self, *args):
1732+
rc, out, err = script_helper.assert_python_ok('-m', 'tarfile', *args)
1733+
return out
1734+
1735+
def tarfilecmd_failure(self, *args):
1736+
return script_helper.assert_python_failure('-m', 'tarfile', *args)
1737+
1738+
def make_simple_tarfile(self, tar_name):
1739+
files = [support.findfile('tokenize_tests.txt'),
1740+
support.findfile('tokenize_tests-no-coding-cookie-'
1741+
'and-utf8-bom-sig-only.txt')]
1742+
self.addCleanup(support.unlink, tar_name)
1743+
with tarfile.open(tar_name, 'w') as tf:
1744+
for tardata in files:
1745+
tf.add(tardata, arcname=os.path.basename(tardata))
1746+
1747+
def test_test_command(self):
1748+
for tar_name in (tarname, gzipname, bz2name, xzname):
1749+
for opt in '-t', '--test':
1750+
out = self.tarfilecmd(opt, tar_name)
1751+
self.assertEqual(out, b'')
1752+
1753+
def test_test_command_verbose(self):
1754+
for tar_name in (tarname, gzipname, bz2name, xzname):
1755+
for opt in '-v', '--verbose':
1756+
out = self.tarfilecmd(opt, '-t', tar_name)
1757+
self.assertIn(b'is a tar archive.\n', out)
1758+
1759+
def test_test_command_invalid_file(self):
1760+
zipname = support.findfile('zipdir.zip')
1761+
rc, out, err = self.tarfilecmd_failure('-t', zipname)
1762+
self.assertIn(b' is not a tar archive.', err)
1763+
self.assertEqual(out, b'')
1764+
self.assertEqual(rc, 1)
1765+
1766+
for tar_name in (tarname, gzipname, bz2name, xzname):
1767+
with self.subTest(tar_name=tar_name):
1768+
with open(tar_name, 'rb') as f:
1769+
data = f.read()
1770+
try:
1771+
with open(tmpname, 'wb') as f:
1772+
f.write(data[:511])
1773+
rc, out, err = self.tarfilecmd_failure('-t', tmpname)
1774+
self.assertEqual(out, b'')
1775+
self.assertEqual(rc, 1)
1776+
finally:
1777+
support.unlink(tmpname)
1778+
1779+
def test_list_command(self):
1780+
self.make_simple_tarfile(tmpname)
1781+
with support.captured_stdout() as t:
1782+
with tarfile.open(tmpname, 'r') as tf:
1783+
tf.list(verbose=False)
1784+
expected = t.getvalue().encode(sys.getfilesystemencoding())
1785+
for opt in '-l', '--list':
1786+
out = self.tarfilecmd(opt, tmpname)
1787+
self.assertEqual(out, expected)
1788+
1789+
def test_list_command_verbose(self):
1790+
self.make_simple_tarfile(tmpname)
1791+
with support.captured_stdout() as t:
1792+
with tarfile.open(tmpname, 'r') as tf:
1793+
tf.list(verbose=True)
1794+
expected = t.getvalue().encode(sys.getfilesystemencoding())
1795+
for opt in '-v', '--verbose':
1796+
out = self.tarfilecmd(opt, '-l', tmpname)
1797+
self.assertEqual(out, expected)
1798+
1799+
def test_list_command_invalid_file(self):
1800+
zipname = support.findfile('zipdir.zip')
1801+
rc, out, err = self.tarfilecmd_failure('-l', zipname)
1802+
self.assertIn(b' is not a tar archive.', err)
1803+
self.assertEqual(out, b'')
1804+
self.assertEqual(rc, 1)
1805+
1806+
def test_create_command(self):
1807+
files = [support.findfile('tokenize_tests.txt'),
1808+
support.findfile('tokenize_tests-no-coding-cookie-'
1809+
'and-utf8-bom-sig-only.txt')]
1810+
for opt in '-c', '--create':
1811+
try:
1812+
out = self.tarfilecmd(opt, tmpname, *files)
1813+
self.assertEqual(out, b'')
1814+
with tarfile.open(tmpname) as tar:
1815+
tar.getmembers()
1816+
finally:
1817+
support.unlink(tmpname)
1818+
1819+
def test_create_command_verbose(self):
1820+
files = [support.findfile('tokenize_tests.txt'),
1821+
support.findfile('tokenize_tests-no-coding-cookie-'
1822+
'and-utf8-bom-sig-only.txt')]
1823+
for opt in '-v', '--verbose':
1824+
try:
1825+
out = self.tarfilecmd(opt, '-c', tmpname, *files)
1826+
self.assertIn(b' file created.', out)
1827+
with tarfile.open(tmpname) as tar:
1828+
tar.getmembers()
1829+
finally:
1830+
support.unlink(tmpname)
1831+
1832+
def test_create_command_dotless_filename(self):
1833+
files = [support.findfile('tokenize_tests.txt')]
1834+
try:
1835+
out = self.tarfilecmd('-c', dotlessname, *files)
1836+
self.assertEqual(out, b'')
1837+
with tarfile.open(dotlessname) as tar:
1838+
tar.getmembers()
1839+
finally:
1840+
support.unlink(dotlessname)
1841+
1842+
def test_create_command_dot_started_filename(self):
1843+
tar_name = os.path.join(TEMPDIR, ".testtar")
1844+
files = [support.findfile('tokenize_tests.txt')]
1845+
try:
1846+
out = self.tarfilecmd('-c', tar_name, *files)
1847+
self.assertEqual(out, b'')
1848+
with tarfile.open(tar_name) as tar:
1849+
tar.getmembers()
1850+
finally:
1851+
support.unlink(tar_name)
1852+
1853+
def test_extract_command(self):
1854+
self.make_simple_tarfile(tmpname)
1855+
for opt in '-e', '--extract':
1856+
try:
1857+
with support.temp_cwd(tarextdir):
1858+
out = self.tarfilecmd(opt, tmpname)
1859+
self.assertEqual(out, b'')
1860+
finally:
1861+
support.rmtree(tarextdir)
1862+
1863+
def test_extract_command_verbose(self):
1864+
self.make_simple_tarfile(tmpname)
1865+
for opt in '-v', '--verbose':
1866+
try:
1867+
with support.temp_cwd(tarextdir):
1868+
out = self.tarfilecmd(opt, '-e', tmpname)
1869+
self.assertIn(b' file is extracted.', out)
1870+
finally:
1871+
support.rmtree(tarextdir)
1872+
1873+
def test_extract_command_different_directory(self):
1874+
self.make_simple_tarfile(tmpname)
1875+
try:
1876+
with support.temp_cwd(tarextdir):
1877+
out = self.tarfilecmd('-e', tmpname, 'spamdir')
1878+
self.assertEqual(out, b'')
1879+
finally:
1880+
support.rmtree(tarextdir)
1881+
1882+
def test_extract_command_invalid_file(self):
1883+
zipname = support.findfile('zipdir.zip')
1884+
with support.temp_cwd(tarextdir):
1885+
rc, out, err = self.tarfilecmd_failure('-e', zipname)
1886+
self.assertIn(b' is not a tar archive.', err)
1887+
self.assertEqual(out, b'')
1888+
self.assertEqual(rc, 1)
1889+
1890+
17271891
class ContextManagerTest(unittest.TestCase):
17281892

17291893
def test_basic(self):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ Core and Builtins
6868
Library
6969
-------
7070

71+
- Issue #13477: Added command line interface to the tarfile module.
72+
Original patch by Berker Peksag.
73+
7174
- Issue #19674: inspect.signature() now produces a correct signature
7275
for some builtins.
7376

0 commit comments

Comments
 (0)