Call a shell command from Python (was: Calling Bash Command From Python)

Wildman best_lay at yahoo.com
Mon Oct 31 10:49:08 EDT 2016


On Mon, 31 Oct 2016 15:44:13 +1100, Ben Finney wrote:

> Wildman via Python-list <python-list at python.org> writes:
> 
>> Python 2.7.9 on Linux
>>
>> Here is a bash command that I want to run from a python
>> program:  sudo grep "^user\:" /etc/shadow
> 
> Some points to note:
> 
> * Those commands are not special to Bash, or any particular shell. They
>   invoke commands, without AFAIK any specific Bash features. So this is
>   asking rather to invoke a shell command.

Yes, I know.  I perhaps should have used the word "shell" instead
of bash.  However, bash is a shell.

>   Nothing wrong with that; but on that basis, I've changed the subject
>   field.
> 
> * You're asking to invoke the ‘sudo’ command, which itself is designed
>   to switch to a separate user identity and run another program.

Yes, I know.

> * The above command is (I assume) typed into a shell, but your Python
>   program never invokes Bash or any other shell.

The program is run in a shell so invocation is not needed.

>> If I enter the command directly into a terminal it works perfectly.
> 
> Note that ‘sudo’ is specifically designed to be invoked interactively,
> seeking to verify that the current user has credentials to run the
> command.
> 
> Note further that ‘sudo’ will record when the *current user session*
> last invoked ‘sudo’ and seek re-verification if that is too long in the
> past.
> 
> Both of these are security measures, and are designed to avoid
> non-interactive use of ‘sudo’. Rather, it's meant to be used
> interactively by a real, present human with credentials to run the
> command.

Yes, I know all that.

>> If I run it from a python program it returns an empty string.
> 
> You can also check the exit status of a command; ‘grep’ will give
> different exit status for a match versus no match.
> 
>> Below is the code I am using.  Suggestions
>> appreciated.
>>
>> cmdlist = ["sudo", "grep", '"^$USER\:"', "/etc/shadow"]
> 
> One immediate difference I see is that you specify different arguments
> to ‘grep’. You have a different pattern for each command.
> 
> * The ‘^user\:’ pattern matches “user\:” at the start of a line.
> 
> * The ‘^$USER\:’ pattern I think won't match anything, since “$” matches
>   end-of-line and then you expect further characters *past* the end of
>   the line. I think that will always fail to match any line.

Yes, the '^' indicates the start of the line and the ':' indicates
the character where to stop.  The colon has a special meaning so it
has to be escaped, '\:'.  The dollar sign precedes a variable.  In
this case it is an environment variable.

The Linux shadow file contains information about the users of the
system.  It has the user name, encrypted password, salt, encryption
type and other information.  The format is such that the user name
is at the start of the line ending with a colon.  Here is an example:

wildman:$6$hODbsJJp$/NWGXZ3fMIVB4U.v/oLtAv.CnL0l0I39.IwsDx1ZAlKW3wUSjTfwJdnQvOMpYNbqNqqFfZ52vgYWBmnjsaX9R.:16177:0:99999:7:::


>> p = subprocess.Popen(cmdlist,
>>                      stdout=subprocess.PIPE,
>>                      stderr=subprocess.PIPE)
>> shadow, err = p.communicate()
> 
> Maybe you are expecting Bash to be involved somehow (and so “$USER” will
> be substituted by Bash with some other value). That's not what happens.

No, the shell is already running.  And $USER will be substituted by the
name of the user that invoked the shell.

> Instead, the ‘subprocess.Popen.communicate’ method will invoke the
> program directly, without involving a shell. See the documentation for
> ‘subprocess.Popen’.

I will look into that.  Thanks for the reply.

-- 
<Wildman> GNU/Linux user #557453
The cow died so I don't need your bull!



More information about the Python-list mailing list