-1

I have bash oneliner and I can't use implement it into python3 and running it on Rhel server.

 for i in {5000..10000}; do if grep "no such user" <(id $i 2>&1 ) > /dev/null; then echo $i ; break ; fi; done

I've already tried

print(subprocess.check_output('bash -c "for i in {5000..10000}; do if grep "no such user" <(id $i 2>&1 ) > /dev/null; then echo $i ; break ; fi ; done" ', shell=True).decode())

and this

p1 = Popen(["for i in {5000..10000}; do if grep "no such user" <(id $i 2>&1 ) > /dev/null; then echo $i ; break ; fi; done"], stdout=PIPE)

print p1.communicate()

Tried also this

command = '''
    for i in {5000..10000}
do
    if grep "no such user" <(id $i 2>&1 ) > /dev/null
    then echo $i
    break
    fi
done
'''
        uid = subprocess.run(command, capture_output=True, shell=True)

And I always get SyntaxError: invalid syntax or

CompletedProcess(args='\n    for i in {5000..10000}\ndo\n    if grep "no such user" <(id $i 2>&1 ) > /dev/null\n    then echo $i\n    break\n    fi\ndone\n', returncode=1, stdout=b'', stderr=b'/bin/sh: -c: line 3: syntax error near unexpected token `(\'\n/bin/sh: -c: line 3: `    if grep "no such user" <(id $i 2>&1 ) > /dev/null\'\n')

Can you please help me and say what am I doing wrong? I've lost countless hours of debugging it and hope on your help.

Durable Metal
  • 35
  • 1
  • 6
  • 1
    Note that this is a _ridiculously_ inefficient shell script. There's no good reason to run `id` and `grep` thousands of times when you could just ask the operating system to retrieve the `passwd` entries directly. – Charles Duffy Jul 20 '22 at 17:37
  • 1
    The `shell=True` version isn't expected to work because it uses `/bin/sh`, not `bash`, and your code has a bunch of bash-specific logic. – Charles Duffy Jul 20 '22 at 17:37
  • 1
    The version where you're using double quotes to surround a shell script that _contains_ double quotes also isn't expected to work, because that's not valid Python string syntax (unless you use backslashes inside your string to escape the inner quotes). – Charles Duffy Jul 20 '22 at 17:38
  • 1
    The very first version, where you have a `sh` script starting `bash` to run a `bash` script... just thinking about how that'll parse makes my head hurt. Don't do that. – Charles Duffy Jul 20 '22 at 17:39
  • 1
    ...so, a more efficient pure-shell way to do what you're aiming for (that still uses bash-only `{1..2}` syntax) might be `printf '%s\n' {50000..10000} | xargs -d $'\n' getent passwd | cut -d: -f3`. I would expect that to be at least an order of magnitude faster than your current code. – Charles Duffy Jul 23 '22 at 18:58

2 Answers2

2

Do the loop and output checking in Python.

for i in range(5000, 10001):
    output = subprocess.check_output(['id', str(i)], stderr=subprocess.DEVNULL, encoding='utf-8')
    if 'no such user' in output:
        print(i)
        break

Python also has a pwd module for searching the user database.

from pwd import getpwuid

for i in range(5000, 10001):
    try:
        getpwuid(i)
    except KeyError:
        print(i)
        break
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    Re: library function that does what `id $i` does -- https://pypi.org/project/getent/; one could also use FFI to call the libc function directly without too much work. Also relevant is [python script to list users and groups](https://stackoverflow.com/questions/421618/python-script-to-list-users-and-groups) – Charles Duffy Jul 20 '22 at 17:39
  • 2
    @CharlesDuffy Those don't look so easy to use for this (the documentation of `getent` is very spare, I don't know if it takes a uid argument), but I found the built-in `pwd` module. – Barmar Jul 20 '22 at 17:49
  • 1
    FWIW, yes, `getent` can take a uid in place of a name; `getent passwd 0` properly returns the entry for `root`, and `getent passswd 0 1 2 3 4` (on the Linux box I have entry) returns first the entry for root, and then the one for the dbus user, that being the account my distro assigns to UID 4, while ignoring those UIDs that have no account assigned -- so one can pass several UIDs on each command line instead of doing a separate invocation for each, allowing xargs-type collapse. – Charles Duffy Jul 23 '22 at 18:56
2

While the better answer is to do this all in Python (which will be vastly more efficient), there's no reason you can't do it with a subprocess.

Use a triple-quoted raw string so your bash code is stored in a Python string without anything munging it:

#!/usr/bin/env python
import subprocess

bash_code=r'''
for i in {5000..10000}; do if grep "no such user" <(id "$i" 2>&1 ) > /dev/null; then echo "$i" ; break ; fi; done
'''
p = subprocess.run(['bash', '-c', bash_code], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(f"stdout is: {f.stdout}")
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441