Repository: conan-io/conan

Issue:
Add options to make conan inspect more machine friendly
I would like to make `conan inspect` more machine friendly.  Here are two things that would help me:

Add an option where the label will not be included on the attribute.  For example, currently if you run
```
$ conan inspect . -a name
name: service-common
```
Then a script would have to parse the actual name from the output.  I would like to have an option that would remove the label, like:
```
$ conan inspect . --no-label -a name
service-common
```
Also, I'd like a way to have the JSON output sent to stdout.  For example:
```
$ conan inspect . -j -
{"name": "service-common", "version": null, "url": "http://bitbucket-idb:7990/scm/deal/falcon.git", "homepage": null, "license": "Copyright (c) 2018 Tradeweb", "author": null, "description": "Falcon Base Service common library", "topics": null, "generators": ["cmake"], "exports": [], "exports_sources": [], "short_paths": false, "apply_env": true, "build_policy": null}
```

Thanks,

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index 0548e3d5a..505144716 100644
--- a/README.rst
+++ b/README.rst
@@ -1,25 +1,28 @@
+|Logo|
+
 Conan
 =====
 
-A distributed, open-source, C/C++ package manager.
+A distributed, open-source (MIT), C/C++ package manager.
 
-+------------------------+-------------------------+
-| **master**             | **develop**             |
-+========================+=========================+
-| |Build Status Master|  | |Build Status Develop|  |
-+------------------------+-------------------------+
++------------------------+-------------------------+-------------------------+
+| **master**             | **develop**             |  **Coverage**           |
++========================+=========================+=========================+
+| |Build Status Master|  | |Build Status Develop|  |  |Develop coverage|     |
++------------------------+-------------------------+-------------------------+
 
 
-+------------------------+---------------------------+---------------------------------------------+
-| **Coverage master**    | **Coverage develop**      | **Coverage graph**                          |
-+========================+===========================+=============================================+
-| |Master coverage|      | |Develop coverage|        | |Coverage graph|                            |
-+------------------------+---------------------------+---------------------------------------------+
+- Homepage: https://conan.io/
+- Docs: https://docs.conan.io/en/latest/
+- Slack: https://cpplang.now.sh/ (#conan channel)
+- Twitter: https://twitter.com/conan_io
 
 
 Setup
 =====
 
+Please read https://docs.conan.io/en/latest/installation.html
+
 From binaries
 -------------
 
@@ -250,6 +253,9 @@ License
    :height: 50px
    :width: 50 px
    :alt: Conan develop coverage
+   
+.. |Logo| image:: https://conan.io/img/jfrog_conan_logo.png
+
 
 .. _`pip docs`: https://pip.pypa.io/en/stable/installing/
 
diff --git a/conans/client/command.py b/conans/client/command.py
index 08ce1705a..9a4a7f98f 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -208,10 +208,21 @@ class Command(object):
                             action=OnceArgument)
         parser.add_argument("-j", "--json", default=None, action=OnceArgument,
                             help='json output file')
+        parser.add_argument('--raw', default=None, action=OnceArgument,
+                            help='Print just the value of the requested attribute')
 
         args = parser.parse_args(*args)
-        result = self._conan.inspect(args.path_or_reference, args.attribute, args.remote)
-        Printer(self._user_io.out).print_inspect(result)
+
+        if args.raw and args.attribute:
+            raise ConanException("Argument '--raw' is incompatible with '-a'")
+
+        if args.raw and args.json:
+            raise ConanException("Argument '--raw' is incompatible with '--json'")
+
+        attributes = [args.raw, ] if args.raw else args.attribute
+
+        result = self._conan.inspect(args.path_or_reference, attributes, args.remote)
+        Printer(self._user_io.out).print_inspect(result, raw=args.raw)
         if args.json:
             json_output = json.dumps(result)
             if not os.path.isabs(args.json):
@@ -1432,9 +1443,14 @@ class Command(object):
                                          prog="conan workspace")
         subparsers = parser.add_subparsers(dest='subcommand', help='sub-command help')
 
-        install_parser = subparsers.add_parser('install', help='same as a "conan install" command '
-                                               'but using the workspace data from the file')
-        install_parser.add_argument('path', help='path to workspace definition file')
+        install_parser = subparsers.add_parser('install',
+                                               help='same as a "conan install" command'
+                                                    ' but using the workspace data from the file. If'
+                                                    ' no file is provided, it will look for a file'
+                                                    ' named "conanws.yml"')
+        install_parser.add_argument('path', help='path to workspace definition file (it will look'
+                                                 ' for a "conanws.yml" inside if a directory is'
+                                                 ' given)')
         _add_common_install_arguments(install_parser, build_help=_help_build_policies)
 
         args = parser.parse_args(*args)
diff --git a/conans/client/printer.py b/conans/client/printer.py
index e1ab99f00..9f2a17a7d 100644
--- a/conans/client/printer.py
+++ b/conans/client/printer.py
@@ -20,7 +20,7 @@ class Printer(object):
     def __init__(self, out):
         self._out = out
 
-    def print_inspect(self, inspect):
+    def print_inspect(self, inspect, raw=False):
         for k, v in inspect.items():
             if k == "default_options":
                 if isinstance(v, str):
@@ -29,12 +29,18 @@ class Printer(object):
                     v = OptionsValues(v)
                 elif isinstance(v, list):
                     v = OptionsValues(tuple(v))
-            if isinstance(v, (dict, OptionsValues)):
-                self._out.writeln("%s:" % k)
-                for ok, ov in sorted(v.items()):
-                    self._out.writeln("    %s: %s" % (ok, ov))
+                elif isinstance(v, dict):
+                    v = OptionsValues(v)
+
+            if raw:
+                self._out.write(str(v))
             else:
-                self._out.writeln("%s: %s" % (k, str(v)))
+                if isinstance(v, (dict, OptionsValues)):
+                    self._out.writeln("%s:" % k)
+                    for ok, ov in sorted(v.items()):
+                        self._out.writeln("    %s: %s" % (ok, ov))
+                else:
+                    self._out.writeln("%s: %s" % (k, str(v)))
 
     def print_info(self, data, _info, package_filter=None, show_paths=False, show_revisions=False):
         """ Print in console the dependency information for a conan file
diff --git a/conans/client/tools/apple.py b/conans/client/tools/apple.py
index e6ea26483..6657463fa 100644
--- a/conans/client/tools/apple.py
+++ b/conans/client/tools/apple.py
@@ -1,7 +1,8 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 import os
-import subprocess
+
+from conans.client.tools.oss import check_output
 
 
 def is_apple_os(os_):
@@ -72,7 +73,7 @@ class XCRun(object):
 
     def _invoke(self, args):
         def cmd_output(cmd):
-            return subprocess.check_output(cmd).decode().strip()
+            return check_output(cmd).strip()
 
         command = ['xcrun', '-find']
         if self.sdk:
diff --git a/conans/client/tools/files.py b/conans/client/tools/files.py
index 40495f114..ec1c0aecb 100644
--- a/conans/client/tools/files.py
+++ b/conans/client/tools/files.py
@@ -54,7 +54,7 @@ def unzip(filename, destination=".", keep_permissions=False, pattern=None, outpu
     """
     Unzip a zipped file
     :param filename: Path to the zip file
-    :param destination: Destination folder
+    :param destination: Destination folder (or file for .gz files)
     :param keep_permissions: Keep the zip permissions. WARNING: Can be
     dangerous if the zip was not created in a NIX system, the bits could
     produce undefined permission schema. Use this option only if you are sure
@@ -70,6 +70,13 @@ def unzip(filename, destination=".", keep_permissions=False, pattern=None, outpu
             filename.endswith(".tbz2") or filename.endswith(".tar.bz2") or
             filename.endswith(".tar")):
         return untargz(filename, destination, pattern)
+    if filename.endswith(".gz"):
+        import gzip
+        with gzip.open(filename, 'rb') as f:
+            file_content = f.read()
+        target_name = filename[:-3] if destination == "." else destination
+        save(target_name, file_content)
+        return
     if filename.endswith(".tar.xz") or filename.endswith(".txz"):
         if six.PY2:
             raise ConanException("XZ format not supported in Python 2. Use Python 3 instead")
diff --git a/conans/client/tools/oss.py b/conans/client/tools/oss.py
index 3a385c43b..1fe9db8e3 100644
--- a/conans/client/tools/oss.py
+++ b/conans/client/tools/oss.py
@@ -5,7 +5,7 @@ import subprocess
 import sys
 import tempfile
 from subprocess import CalledProcessError, PIPE
-
+import six
 from conans.client.tools.env import environment_append
 from conans.client.tools.files import load, which
 from conans.errors import ConanException
@@ -155,7 +155,7 @@ class OSInfo(object):
         if self.is_linux:
             return self.linux_distro in ["arch", "manjaro"]
         elif self.is_windows and which('uname.exe'):
-            uname = subprocess.check_output(['uname.exe', '-s']).decode()
+            uname = check_output(['uname.exe', '-s'])
             return uname.startswith('MSYS_NT') and which('pacman.exe')
         return False
 
@@ -299,7 +299,7 @@ class OSInfo(object):
         try:
             # the uname executable is many times located in the same folder as bash.exe
             with environment_append({"PATH": [os.path.dirname(custom_bash_path)]}):
-                ret = subprocess.check_output(command, shell=True, ).decode().strip().lower()
+                ret = check_output(command).strip().lower()
                 return ret
         except Exception:
             return None
@@ -436,10 +436,13 @@ def get_gnu_triplet(os_, arch, compiler=None):
     return "%s-%s" % (machine, op_system)
 
 
-def check_output(cmd, folder=None, return_code=False):
+def check_output(cmd, folder=None, return_code=False, stderr=None):
     tmp_file = tempfile.mktemp()
     try:
-        process = subprocess.Popen("{} > {}".format(cmd, tmp_file), shell=True, stderr=PIPE, cwd=folder)
+        stderr = stderr or PIPE
+        cmd = cmd if isinstance(cmd, six.string_types) else subprocess.list2cmdline(cmd)
+        process = subprocess.Popen("{} > {}".format(cmd, tmp_file), shell=True,
+                                   stderr=stderr, cwd=folder)
         process.communicate()
 
         if return_code:
@@ -453,5 +456,5 @@ def check_output(cmd, folder=None, return_code=False):
     finally:
         try:
             os.unlink(tmp_file)
-        except:
+        except Exception:
             pass
diff --git a/conans/client/tools/pkg_config.py b/conans/client/tools/pkg_config.py
index 2dab3f6b1..87e32cb1f 100644
--- a/conans/client/tools/pkg_config.py
+++ b/conans/client/tools/pkg_config.py
@@ -3,13 +3,14 @@
 
 import subprocess
 
+from conans.client.tools.oss import check_output
 from conans.errors import ConanException
 
 
 class PkgConfig(object):
     @staticmethod
     def _cmd_output(command):
-        return subprocess.check_output(command).decode().strip()
+        return check_output(command).strip()
 
     def __init__(self, library, pkg_config_executable='pkg-config', static=False, msvc_syntax=False, variables=None,
                  print_errors=True):
diff --git a/conans/client/tools/scm.py b/conans/client/tools/scm.py
index 2383873ba..28094f1d1 100644
--- a/conans/client/tools/scm.py
+++ b/conans/client/tools/scm.py
@@ -218,7 +218,7 @@ class SVN(SCMBase):
 
     def __init__(self, folder=None, runner=None, *args, **kwargs):
         def runner_no_strip(command):
-            return decode_text(subprocess.check_output(command, shell=True))
+            return check_output(command)
         runner = runner or runner_no_strip
         super(SVN, self).__init__(folder=folder, runner=runner, *args, **kwargs)
 
diff --git a/conans/client/tools/win.py b/conans/client/tools/win.py
index da68c0902..a5c2d9e06 100644
--- a/conans/client/tools/win.py
+++ b/conans/client/tools/win.py
@@ -9,7 +9,7 @@ import deprecation
 
 from conans.client.tools import which
 from conans.client.tools.env import environment_append
-from conans.client.tools.oss import OSInfo, detected_architecture
+from conans.client.tools.oss import OSInfo, detected_architecture, check_output
 from conans.errors import ConanException
 from conans.model.version import Version
 from conans.unicode import get_cwd
@@ -303,8 +303,7 @@ def vswhere(all_=False, prerelease=False, products=None, requires=None, version=
         arguments.append("-nologo")
 
     try:
-        output = subprocess.check_output(arguments)
-        output = decode_text(output).strip()
+        output = check_output(arguments).strip()
         # Ignore the "description" field, that even decoded contains non valid charsets for json
         # (ignored ones)
         output = "\n".join([line for line in output.splitlines()
@@ -447,16 +446,11 @@ def vcvars_dict(settings, arch=None, compiler_version=None, force=False, filter_
     cmd = vcvars_command(settings, arch=arch,
                          compiler_version=compiler_version, force=force,
                          vcvars_ver=vcvars_ver, winsdk_version=winsdk_version, output=output)
-    cmd += " && echo __BEGINS__ && set"
-    ret = decode_text(subprocess.check_output(cmd, shell=True))
+    cmd += " && set"
+    ret = check_output(cmd)
     new_env = {}
-    start_reached = False
     for line in ret.splitlines():
         line = line.strip()
-        if not start_reached:
-            if "__BEGINS__" in line:
-                start_reached = True
-            continue
 
         if line == "\n" or not line:
             continue
diff --git a/conans/model/workspace.py b/conans/model/workspace.py
index e8d9762cf..3703d0012 100644
--- a/conans/model/workspace.py
+++ b/conans/model/workspace.py
@@ -39,6 +39,7 @@ class LocalPackage(object):
 
 
 class Workspace(object):
+    default_filename = "conanws.yml"
 
     def generate(self, cwd, graph, output):
         if self._ws_generator == "cmake":
@@ -90,6 +91,10 @@ class Workspace(object):
         self._cache = cache
         self._ws_generator = None
         self._workspace_packages = OrderedDict()  # {reference: LocalPackage}
+
+        if not os.path.isfile(path):
+            path = os.path.join(path, self.default_filename)
+
         self._base_folder = os.path.dirname(path)
         try:
             content = load(path)


Test patch:
diff --git a/conans/test/functional/command/info_folders_test.py b/conans/test/functional/command/info_folders_test.py
index 117120795..dcb3a0e1c 100644
--- a/conans/test/functional/command/info_folders_test.py
+++ b/conans/test/functional/command/info_folders_test.py
@@ -6,13 +6,13 @@ import unittest
 from textwrap import dedent
 
 from conans.client import tools
+from conans.client.tools.oss import check_output
 from conans.model.ref import ConanFileReference, PackageReference
 from conans.paths import CONANFILE
 from conans.test.utils.test_files import temp_folder
-from conans.test.utils.tools import NO_SETTINGS_PACKAGE_ID, TestClient,\
+from conans.test.utils.tools import NO_SETTINGS_PACKAGE_ID, TestClient, \
     TestServer
 
-
 conanfile_py = """
 from conans import ConanFile
 
@@ -181,14 +181,13 @@ class InfoFoldersTest(unittest.TestCase):
 
         # Retrieve ACLs from short_folder
         try:
-            short_folder_acls = subprocess.check_output("cacls %s" % short_folder,
-                                                        stderr=subprocess.STDOUT)
+            short_folder_acls = check_output("cacls %s" % short_folder, stderr=subprocess.STDOUT)
         except subprocess.CalledProcessError as e:
             raise Exception("Error %s getting ACL from short_folder: '%s'." % (e, short_folder))
 
         # Check user has full control
         user_acl = "%s\\%s:(OI)(CI)F" % (current_domain, current_user)
-        self.assertIn(user_acl.encode(), short_folder_acls)
+        self.assertIn(user_acl, short_folder_acls)
 
     def test_direct_conanfile(self):
         client = TestClient()
diff --git a/conans/test/functional/command/inspect_test.py b/conans/test/functional/command/inspect_test.py
index e43f61f3e..3c3000695 100644
--- a/conans/test/functional/command/inspect_test.py
+++ b/conans/test/functional/command/inspect_test.py
@@ -338,3 +338,81 @@ default_options:
     bar: True
     foo: True
 """, client.out)
+
+
+class InspectRawTest(unittest.TestCase):
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        
+        class Pkg(ConanFile):
+            name = "MyPkg"
+            version = "1.2.3"
+            author = "John Doe"
+            url = "https://john.doe.com"
+            settings = "os", "arch", "build_type", "compiler"
+            options = {"foo": [True, False], "bar": [True, False]}
+            default_options = "foo=True", "bar=True"
+            
+            _private = "Nothing"
+
+            def build(self):
+                pass
+        """)
+
+    def test_no_field_or_multiple(self):
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile})
+        client.run("inspect . --raw", assert_error=True)
+        client.run("inspect . --raw=name --raw=version", assert_error=True)
+
+    def test_incompatible_commands(self):
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile})
+
+        client.run("inspect . --raw=name -a=version", assert_error=True)
+        self.assertIn("ERROR: Argument '--raw' is incompatible with '-a'", client.out)
+
+        client.run("inspect . --raw=name --json=tmp.json", assert_error=True)
+        self.assertIn("ERROR: Argument '--raw' is incompatible with '--json'", client.out)
+
+    def test_invalid_field(self):
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile})
+        client.run("inspect . --raw=not_exists", assert_error=True)
+        self.assertIn("ERROR: 'Pkg' object has no attribute 'not_exists'", client.out)
+
+    def test_private_field(self):
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile})
+        client.run("inspect . --raw=_private")
+        self.assertEqual("Nothing", client.out)
+
+    def test_settings(self):
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile})
+        client.run("inspect . --raw=settings")
+        self.assertEqual("('os', 'arch', 'build_type', 'compiler')", client.out)
+
+    def test_options_dict(self):
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile})
+        client.run("inspect . --raw=options")
+        self.assertEqual("{'foo': [True, False], 'bar': [True, False]}", client.out)
+
+    def test_default_options_list(self):
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile})
+        client.run("inspect . --raw=default_options")
+        self.assertEqual("bar=True\nfoo=True", client.out)
+
+    def test_default_options_dict(self):
+        client = TestClient()
+        conanfile = textwrap.dedent("""
+            from conans import ConanFile
+            
+            class Lib(ConanFile):
+                default_options = {"dict": True, "list": False}
+            """)
+        client.save({"conanfile.py": conanfile})
+        client.run("inspect . --raw=default_options")
+        self.assertEqual("dict=True\nlist=False", client.out)
diff --git a/conans/test/functional/generators/virtualbuildenv_test.py b/conans/test/functional/generators/virtualbuildenv_test.py
index fc5c676fa..8fe528747 100644
--- a/conans/test/functional/generators/virtualbuildenv_test.py
+++ b/conans/test/functional/generators/virtualbuildenv_test.py
@@ -1,9 +1,9 @@
 import os
 import platform
-import subprocess
 import unittest
 
 from conans import load
+from conans.client.tools.oss import check_output
 from conans.test.utils.tools import TestClient
 
 
@@ -32,7 +32,7 @@ class TestConan(ConanFile):
         def env_output_to_dict(env_output):
             env = {}
             for line in env_output.splitlines():
-                tmp = line.decode().split("=")
+                tmp = line.split("=")
                 # OLDPWD is cleared when a child script is started
                 if tmp[0] not in ["SHLVL", "_", "PS1", "OLDPWD"]:
                     env[tmp[0]] = tmp[1].replace("\\", "/")
@@ -56,7 +56,7 @@ class TestConan(ConanFile):
         client = TestClient(path_with_spaces=False)
         client.save({"conanfile.py": conanfile})
         client.run("install .")
-        output = subprocess.check_output(env_cmd, shell=True)
+        output = check_output(env_cmd)
         normal_environment = env_output_to_dict(output)
         client.run("install .")
         act_build_file = os.path.join(client.current_folder, "activate_build.%s" % extension)
@@ -67,9 +67,9 @@ class TestConan(ConanFile):
             act_build_content_len = len(load(act_build_file).splitlines())
             deact_build_content_len = len(load(deact_build_file).splitlines())
             self.assertEqual(act_build_content_len, deact_build_content_len)
-        output = subprocess.check_output(get_cmd(act_build_file), shell=True)
+        output = check_output(get_cmd(act_build_file))
         activate_environment = env_output_to_dict(output)
         self.assertNotEqual(normal_environment, activate_environment)
-        output = subprocess.check_output(get_cmd(deact_build_file), shell=True)
+        output = check_output(get_cmd(deact_build_file))
         deactivate_environment = env_output_to_dict(output)
         self.assertEqual(normal_environment, deactivate_environment)
diff --git a/conans/test/functional/workspace/workspace_test.py b/conans/test/functional/workspace/workspace_test.py
index 0373e6ee6..03d748557 100644
--- a/conans/test/functional/workspace/workspace_test.py
+++ b/conans/test/functional/workspace/workspace_test.py
@@ -924,3 +924,55 @@ class Pkg(ConanFile):
         for p in ("HelloC", "HelloB", "HelloA"):
             self.assertIn("add_subdirectory(${PACKAGE_%s_SRC} ${PACKAGE_%s_BUILD})" % (p, p),
                           conanws_cmake)
+
+    def test_default_filename(self):
+        client = TestClient()
+        path_to_editable = os.path.join(client.current_folder, "A")
+
+        project = dedent("""
+            editables:
+                HelloA/0.1@lasote/stable:
+                    path: {path_to_editable}
+            layout: layout
+            workspace_generator: cmake
+            root: HelloA/0.1@lasote/stable
+            """.format(path_to_editable=path_to_editable))
+
+        conanfile = dedent("""
+            from conans import ConanFile
+
+            class Lib(ConanFile):
+                pass
+            """)
+
+        layout = dedent("""
+            [build_folder]
+            [source_folder]
+            """)
+
+        client.save({"conanfile.py": conanfile}, path=path_to_editable)
+        ws_folder = temp_folder()
+        client.save({os.path.join(ws_folder, Workspace.default_filename): project,
+                     os.path.join(ws_folder, "layout"): layout})
+
+        # For the right folder, it works
+        client.run('workspace install "{}"'.format(ws_folder))
+        conanws_cmake = load(os.path.join(client.current_folder, "conanworkspace.cmake"))
+        self.assertIn("macro(conan_workspace_subdirectories)", conanws_cmake)
+
+        # For a non existing folder, it will try to load the default filename (it fails)
+        non_existing = temp_folder()
+        client.run('workspace install "{}"'.format(non_existing), assert_error=True)
+        trial_path = os.path.join(non_existing, Workspace.default_filename)
+        self.assertIn("ERROR: Couldn't load workspace file in {}".format(trial_path), client.out)
+
+        # For an existing file, it will try to use it (will fail because of the format)
+        invalid_file = os.path.join(ws_folder, "layout")
+        client.run('workspace install "{}"'.format(invalid_file), assert_error=True)
+        self.assertIn("ERROR: There was an error parsing", client.out)
+
+        # For an existing folder, without the default file (it will fail looking for it)
+        no_default_file = os.path.join(client.current_folder)
+        client.run('workspace install "{}"'.format(no_default_file), assert_error=True)
+        trial_path = os.path.join(no_default_file, Workspace.default_filename)
+        self.assertIn("ERROR: Couldn't load workspace file in {}".format(trial_path), client.out)
diff --git a/conans/test/integration/run_envronment_test.py b/conans/test/integration/run_envronment_test.py
index 0ed812625..7cfa95894 100644
--- a/conans/test/integration/run_envronment_test.py
+++ b/conans/test/integration/run_envronment_test.py
@@ -1,8 +1,8 @@
 import platform
-import subprocess
 import unittest
 
 from conans.client import tools
+from conans.client.tools.oss import check_output
 from conans.paths import CONANFILE
 from conans.test.utils.cpp_test_files import cpp_hello_conan_files
 from conans.test.utils.tools import TestClient, TestServer
@@ -153,5 +153,5 @@ execute_process(COMMAND say_hello)
                 # running, or in the same line "$ DYLD_LIBRARY_PATH=[path] say_hello"
                 command = "bash -c 'source activate_run.sh && say_hello'"
 
-            output = subprocess.check_output(command, shell=True)
-            self.assertIn("Hello Tool!", str(output))
+            output = check_output(command)
+            self.assertIn("Hello Tool!", output)
diff --git a/conans/test/unittests/client/tools/os_info/osinfo_test.py b/conans/test/unittests/client/tools/os_info/osinfo_test.py
index 443143ca3..b361fcd99 100644
--- a/conans/test/unittests/client/tools/os_info/osinfo_test.py
+++ b/conans/test/unittests/client/tools/os_info/osinfo_test.py
@@ -1,8 +1,8 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-import os
 import unittest
+
 from mock import mock
 
 from conans.client.tools import OSInfo, environment_append, CYGWIN, MSYS2, MSYS, WSL, \
@@ -16,11 +16,11 @@ class OSInfoTest(unittest.TestCase):
         self._uname = None
         self._version = None
 
-    def subprocess_check_output_mock(self, cmd, shell):
+    def subprocess_check_output_mock(self, cmd):
         if cmd.endswith('"uname"'):
-            return self._uname.encode()
+            return self._uname
         elif cmd.endswith('"uname -r"'):
-            return self._version.encode()
+            return self._version
         raise ValueError("don't know how to respond to %s" % cmd)
 
     def test_windows(self):
@@ -55,7 +55,7 @@ class OSInfoTest(unittest.TestCase):
             self.assertFalse(OSInfo().is_solaris)
 
             with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
-                with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                with mock.patch('conans.client.tools.oss.check_output', new=self.subprocess_check_output_mock):
                     self.assertEqual(OSInfo.uname(), self._uname.lower())
                     self.assertEqual(OSInfo.detect_windows_subsystem(), CYGWIN)
 
@@ -72,7 +72,7 @@ class OSInfoTest(unittest.TestCase):
             self.assertFalse(OSInfo().is_solaris)
 
             with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
-                with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                with mock.patch('conans.client.tools.oss.check_output', new=self.subprocess_check_output_mock):
                     self.assertEqual(OSInfo.uname(), self._uname.lower())
                     self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS)
 
@@ -89,7 +89,7 @@ class OSInfoTest(unittest.TestCase):
             self.assertFalse(OSInfo().is_solaris)
 
             with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
-                with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                with mock.patch('conans.client.tools.oss.check_output', new=self.subprocess_check_output_mock):
                     self.assertEqual(OSInfo.uname(), self._uname.lower())
                     self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS2)
 
@@ -106,7 +106,7 @@ class OSInfoTest(unittest.TestCase):
             self.assertFalse(OSInfo().is_solaris)
 
             with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
-                with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                with mock.patch('conans.client.tools.oss.check_output', new=self.subprocess_check_output_mock):
                     self.assertEqual(OSInfo.uname(), self._uname.lower())
                     self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS2)
 
diff --git a/conans/test/unittests/util/detect_test.py b/conans/test/unittests/util/detect_test.py
index f1bc280a2..6498b0ac8 100644
--- a/conans/test/unittests/util/detect_test.py
+++ b/conans/test/unittests/util/detect_test.py
@@ -1,4 +1,3 @@
-import os
 import platform
 import subprocess
 import unittest
@@ -7,6 +6,7 @@ from mock import mock
 
 from conans.client import tools
 from conans.client.conf.detect import detect_defaults_settings
+from conans.client.tools.oss import check_output
 from conans.paths import DEFAULT_PROFILE_NAME
 from conans.test.utils.tools import TestBufferConanOutput
 
@@ -36,12 +36,12 @@ class DetectTest(unittest.TestCase):
             return
 
         try:
-            output = subprocess.check_output(["gcc", "--version"], stderr=subprocess.STDOUT)
+            output = check_output(["gcc", "--version"], stderr=subprocess.STDOUT)
         except subprocess.CalledProcessError:
             # gcc is not installed or there is any error (no test scenario)
             return
 
-        if b"clang" not in output:
+        if "clang" not in output:
             # Not test scenario gcc should display clang in output
             # see: https://stackoverflow.com/questions/19535422/os-x-10-9-gcc-links-to-clang
             raise Exception("Apple gcc doesn't point to clang with gcc frontend anymore! please check")
diff --git a/conans/test/unittests/util/tools_test.py b/conans/test/unittests/util/tools_test.py
index b836d2564..67465155a 100644
--- a/conans/test/unittests/util/tools_test.py
+++ b/conans/test/unittests/util/tools_test.py
@@ -25,6 +25,7 @@ from conans.client.conan_api import ConanAPIV1
 from conans.client.conf import default_client_conf, default_settings_yml
 from conans.client.output import ConanOutput
 from conans.client.tools.files import replace_in_file, which
+from conans.client.tools.oss import check_output
 from conans.client.tools.scm import Git, SVN
 from conans.client.tools.system_pm import AptTool, ChocolateyTool, OSInfo, SystemPackageTool
 from conans.client.tools.win import vcvars_dict, vswhere
@@ -1013,7 +1014,7 @@ compiler:
 
     def vcvars_dict_test(self):
         # https://github.com/conan-io/conan/issues/2904
-        output_with_newline_and_spaces = """__BEGINS__
+        output_with_newline_and_spaces = """
      PROCESSOR_ARCHITECTURE=AMD64
 
 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 158 Stepping 9, GenuineIntel
@@ -1030,18 +1031,18 @@ without_equals_sign
 
 ProgramFiles(x86)=C:\Program Files (x86)
 
-""".encode("utf-8")
+"""
 
         def vcvars_command_mock(settings, arch, compiler_version, force, vcvars_ver, winsdk_version,
                                 output):  # @UnusedVariable
             return "unused command"
 
-        def subprocess_check_output_mock(cmd, shell):
+        def subprocess_check_output_mock(cmd):
             self.assertIn("unused command", cmd)
             return output_with_newline_and_spaces
 
         with mock.patch('conans.client.tools.win.vcvars_command', new=vcvars_command_mock):
-            with mock.patch('subprocess.check_output', new=subprocess_check_output_mock):
+            with patch('conans.client.tools.win.check_output', new=subprocess_check_output_mock):
                 vcvars = tools.vcvars_dict(None, only_diff=False, output=self.output)
                 self.assertEqual(vcvars["PROCESSOR_ARCHITECTURE"], "AMD64")
                 self.assertEqual(vcvars["PROCESSOR_IDENTIFIER"], "Intel64 Family 6 Model 158 Stepping 9, GenuineIntel")
@@ -1284,6 +1285,43 @@ ProgramFiles(x86)=C:\Program Files (x86)
         # Not found error
         self.assertEqual(str(out).count("Waiting 0 seconds to retry..."), 2)
 
+    @attr('slow')
+    def get_gunzip_test(self):
+        # Create a tar file to be downloaded from server
+        tmp = temp_folder()
+        filepath = os.path.join(tmp, "test.txt.gz")
+        import gzip
+        with gzip.open(filepath, "wb") as f:
+            f.write(b"hello world zipped!")
+
+        thread = StoppableThreadBottle()
+
+        @thread.server.get("/test.txt.gz")
+        def get_file():
+            return static_file(os.path.basename(filepath), root=os.path.dirname(filepath),
+                               mimetype="application/octet-stream")
+
+        thread.run_server()
+
+        out = TestBufferConanOutput()
+        with tools.chdir(tools.mkdir_tmp()):
+            tools.get("http://localhost:%s/test.txt.gz" % thread.port, requester=requests,
+                      output=out)
+            self.assertTrue(os.path.exists("test.txt"))
+            self.assertEqual(load("test.txt"), "hello world zipped!")
+        with tools.chdir(tools.mkdir_tmp()):
+            tools.get("http://localhost:%s/test.txt.gz" % thread.port, requester=requests,
+                      output=out, destination="myfile.doc")
+            self.assertTrue(os.path.exists("myfile.doc"))
+            self.assertEqual(load("myfile.doc"), "hello world zipped!")
+        with tools.chdir(tools.mkdir_tmp()):
+            tools.get("http://localhost:%s/test.txt.gz" % thread.port, requester=requests,
+                      output=out, destination="mytemp/myfile.txt")
+            self.assertTrue(os.path.exists("mytemp/myfile.txt"))
+            self.assertEqual(load("mytemp/myfile.txt"), "hello world zipped!")
+
+        thread.stop()
+
     def unix_to_dos_unit_test(self):
 
         def save_file(contents):
@@ -1294,13 +1332,12 @@ ProgramFiles(x86)=C:\Program Files (x86)
 
         fp = save_file(b"a line\notherline\n")
         if platform.system() != "Windows":
-            import subprocess
-            output = subprocess.check_output(["file", fp], stderr=subprocess.STDOUT)
+            output = check_output(["file", fp], stderr=subprocess.STDOUT)
             self.assertIn("ASCII text", str(output))
             self.assertNotIn("CRLF", str(output))
 
             tools.unix2dos(fp)
-            output = subprocess.check_output(["file", fp], stderr=subprocess.STDOUT)
+            output = check_output(["file", fp], stderr=subprocess.STDOUT)
             self.assertIn("ASCII text", str(output))
             self.assertIn("CRLF", str(output))
         else:
@@ -1314,13 +1351,12 @@ ProgramFiles(x86)=C:\Program Files (x86)
 
         fp = save_file(b"a line\r\notherline\r\n")
         if platform.system() != "Windows":
-            import subprocess
-            output = subprocess.check_output(["file", fp], stderr=subprocess.STDOUT)
+            output = check_output(["file", fp], stderr=subprocess.STDOUT)
             self.assertIn("ASCII text", str(output))
             self.assertIn("CRLF", str(output))
 
             tools.dos2unix(fp)
-            output = subprocess.check_output(["file", fp], stderr=subprocess.STDOUT)
+            output = check_output(["file", fp], stderr=subprocess.STDOUT)
             self.assertIn("ASCII text", str(output))
             self.assertNotIn("CRLF", str(output))
         else:
diff --git a/conans/test/utils/tools.py b/conans/test/utils/tools.py
index dd7acabb2..69c167663 100644
--- a/conans/test/utils/tools.py
+++ b/conans/test/utils/tools.py
@@ -9,7 +9,6 @@ import subprocess
 import sys
 import tempfile
 import threading
-import time
 import unittest
 import uuid
 from collections import Counter, OrderedDict
@@ -19,6 +18,7 @@ import bottle
 import nose
 import requests
 import six
+import time
 from mock import Mock
 from six import StringIO
 from six.moves.urllib.parse import quote, urlsplit, urlunsplit
@@ -26,6 +26,7 @@ from webtest.app import TestApp
 
 from conans import tools, load
 from conans.client.cache.cache import ClientCache
+from conans.client.cache.remote_registry import Remotes
 from conans.client.command import Command
 from conans.client.conan_api import Conan, get_request_timeout, migrate_and_get_cache
 from conans.client.conan_command_output import CommandOutputer
@@ -37,6 +38,7 @@ from conans.client.rest.uploader_downloader import IterableToFileAdapter
 from conans.client.tools import environment_append
 from conans.client.tools.files import chdir
 from conans.client.tools.files import replace_in_file
+from conans.client.tools.oss import check_output
 from conans.client.tools.scm import Git, SVN
 from conans.client.tools.win import get_cased_path
 from conans.client.userio import UserIO
@@ -53,9 +55,7 @@ from conans.test.utils.server_launcher import (TESTING_REMOTE_PRIVATE_PASS,
 from conans.test.utils.test_files import temp_folder
 from conans.tools import set_global_instances
 from conans.util.env_reader import get_env
-from conans.util.files import mkdir, save, save_files
-from conans.util.log import logger
-from conans.client.cache.remote_registry import Remotes
+from conans.util.files import mkdir, save_files
 
 NO_SETTINGS_PACKAGE_ID = "5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9"
 ARTIFACTORY_DEFAULT_USER = "admin"
@@ -519,11 +519,10 @@ def create_local_svn_checkout(files, repo_url, rel_project_path=None,
             subprocess.check_output("svn add .", shell=True)
             subprocess.check_output('svn commit -m "{}"'.format(commit_msg), shell=True)
             if SVN.get_version() >= SVN.API_CHANGE_VERSION:
-                rev = subprocess.check_output("svn info --show-item revision",
-                                              shell=True).decode().strip()
+                rev = check_output("svn info --show-item revision").strip()
             else:
                 import xml.etree.ElementTree as ET
-                output = subprocess.check_output("svn info --xml", shell=True).decode().strip()
+                output = check_output("svn info --xml").strip()
                 root = ET.fromstring(output)
                 rev = root.findall("./entry")[0].get("revision")
         project_url = repo_url + "/" + quote(rel_project_path.replace("\\", "/"))