shellwhale
shellwhale

Reputation: 902

Can a program detect if it is launched via shell or not?

The ls command is giving me a different stdout if shell is set to true. Why is that ?

Is there an underlying concept that allow a program (ls in this case) to detect if it is launched via shell or not ?

I noticed that both p1 and p2share the same stdout on Windows, but not on Linux.

import subprocess

cmd = ['ls', '-la']
# Using shell
p1 = subprocess.run(executable=cmd[0], args=cmd[1:], shell=True, text=True, capture_output=True)
# Without using shell
p2 = subprocess.run(executable=cmd[0], args=cmd[1:], shell=False, text=True, capture_output=True)

print(p1.stdout)
print(p2.stdout)

Output on Linux

total 12
drwxr-xr-x  2 root root 4096 Feb 20 18:25 .
drwx------ 10 root root 4096 Feb 20 18:51 ..
-rw-r--r--  1 root root  269 Feb 20 18:57 test.py

test.py

Upvotes: 0

Views: 420

Answers (2)

that other guy
that other guy

Reputation: 123600

The Python documentation is fuzzy, but the behavior can't hide from strace.

Your Python code:

cmd = ['ls', '-la']
p1 = subprocess.run(executable=cmd[0], args=cmd[1:], shell=True, text=True, capture_output=True)
p2 = subprocess.run(executable=cmd[0], args=cmd[1:], shell=False, text=True, capture_output=True)

Turns into (strace -f -e execve python3 foo.py):

[pid 143557] execve("/bin/ls", ["ls", "-c", "-la"], 0x7fffc1235340 /* 34 vars */) = 0
[pid 143558] execve("/bin/ls", ["-la"], 0x7fffc1235340 /* 34 vars */) = 0

Which is equivalent to running these shell commands, which you can confirm gives the same result even though both are being executed from a shell.

ls -c -la           # Generally equivalent to: ls -lca
( exec -a -la ls )  # Generally equivalent to: ls

From this we can deduce the behavior.

If shell=True, the executable is invoked. The argument list is the executable, followed by the shell standard -c flag, followed by args. This makes more sense in the case of executable='bash', args=['ls -la'].

If shell=False, the executable is invoked. The argument list is args. This mimics execve.

So essentially no, there is no detection going on here. It's simply two different invocations of ls, and neither is what you wanted.

Upvotes: 2

JacobIRR
JacobIRR

Reputation: 8966

There is no way to tell by looking at the return value of the run() call like you are.

The returned value (p1, which is a CompletedProcess) does not store an attribute for shell:

>>> p1.__dict__
{'args': 'ls', 'returncode': 0, 'stdout': None, 'stderr': None}

Upvotes: 1

Related Questions