diff -r 85b2aa520c84 -r 9e03e149be3b Doc/library/os.rst --- a/Doc/library/os.rst Sat Feb 28 10:46:00 2015 -0500 +++ b/Doc/library/os.rst Thu Mar 19 14:10:29 2015 -0500 @@ -219,6 +219,18 @@ .. versionadded:: 3.2 +.. function:: get_shell_executable() + + Return a path to the standard system shell executable -- + :command:`sh` program on POSIX (default: :const:`'/bin/sh'`) or + :envvar:`ComSpec` command processor on Windows (default: + :file:`%SystemRoot%\\system32\\cmd.exe`). + + Availability: Unix, Windows + + .. versionadded:: 3.5 + + .. function:: getegid() Return the effective group id of the current process. This corresponds to the diff -r 85b2aa520c84 -r 9e03e149be3b Lib/os.py --- a/Lib/os.py Sat Feb 28 10:46:00 2015 -0500 +++ b/Lib/os.py Thu Mar 19 14:10:29 2015 -0500 @@ -616,6 +616,32 @@ return path_list.split(pathsep) +if sys.platform == "win32": + def get_shell_executable(): + """Return path to the command processor specified by %ComSpec%. + + Default: %SystemRoot%\system32\cmd.exe + """ + return (environ.get('ComSpec') or + path.join(environ.get('SystemRoot', r'C:\Windows'), + r'system32\cmd.exe')) +else: # POSIX + def get_shell_executable(): + """Return path to the standard system shell executable. + + Default: '/bin/sh' + """ + for dirpath in defpath.split(pathsep): + if dirpath: # exclude '' (cwd) + sh = path.join(dirpath, 'sh') + if access(sh, F_OK | X_OK) and path.isfile(sh): + #NOTE: allow symlinks e.g., /bin/sh on Ubuntu may be dash + return sh + # normally, we should not get here, defpath should contain the + # path to the standard shell + return '/bin/sh' + + # Change environ to automatically call putenv(), unsetenv if they exist. from _collections_abc import MutableMapping diff -r 85b2aa520c84 -r 9e03e149be3b Lib/test/test_os.py --- a/Lib/test/test_os.py Sat Feb 28 10:46:00 2015 -0500 +++ b/Lib/test/test_os.py Thu Mar 19 14:10:29 2015 -0500 @@ -760,6 +760,35 @@ self.assertTrue(cm.exception.__suppress_context__) +class ShellExecutableTests(unittest.TestCase): + # test os.get_shell_executable() + def _test_get_shell_executable(self, env): + saved_environ = os.environ + try: + os.environ = env # test with env environment + sh = os.get_shell_executable() + self.assertTrue(sh) # not empty, not None + self.assertTrue(os.path.exists(sh)) + option = "/c" if sys.platform == 'win32' else "-c" + #NOTE: `exit` should work even with an empty environment + self.assertEqual(subprocess.call([sh, option, "exit 5"]), 5) + finally: + os.environ = saved_environ + + def test_get_shell_executable(self): + self._test_get_shell_executable(os.environ) + + def test_get_shell_executable_invalid_nonempty_path(self): + env = {'SystemRoot': os.environ.get('SystemRoot'), + 'PATH': '\0'} + self._test_get_shell_executable(env) + + @unittest.skipIf(sys.platform == 'win32', + "Empty env. is not supported on Win32") + def test_get_shell_executable_empty_environment(self): + self._test_get_shell_executable({}) + + class WalkTests(unittest.TestCase): """Tests for os.walk()."""