how to return strings from a list comprehension

Viewed 86

I'm building a shell and I have a user input command. I want to glob the command if it contains a * or a ?

Because I am using the subprocess module, my commands are being stored in a list. With piping, it can become a list of lists.

Thus, my data is potentially coming in as [["first", "set", "of", "commands"],[["second", "set", "of", "commands"]]

Now, let us suppose that there is an * in one of the commands and I need to glob. the python glob module returns a list. Because of this, if my user input ls *.py in a directory with 3 python files, globing would return a nested loop ["ls, ["1.py", "2.py", "3.py"]] but I need it to return a flat list like this: ["ls", "1.py", "2.py", "3.py"]

I can make this work with the following code,

# commands is my list of lists
for cmd in commands:
    for expr in cmd:
        if "*" in expr or "?" in expr:
            expr_index = cmd.index(expr)
            globbed = glob(expr)
            cmd[expr_index] = globbed.pop(0)
            for item in globbed:
                cmd.insert(expr_index, item)
                expr_index += 1

now, while it works, it is just ugly as sin and I feel like there should be a better way to do it.

Ideally, I want to use a list comprehension but I'm not sure it's even possible to return what I need from the comprehension.

It would be great if I could do something like:

# again, commands is my list of lists
globbed_commands = [
    [item for item in glob(expr) if "*" in expr or "?" in expr else expr for expr in cmd]
    for cmd in commands
]

obviously, this throws a syntax error. Is it even possible to achieve this using a list comprehension? And if it is, what am I missing?

1 Answers

The most elegant solution here is to simply build a new list instead of messing around with indices.

globbed_commands = []
for cmd in commands:
    new_cmd = []
    for expr in cmd:
        if "*" in expr or "?" in expr:
            new_cmd.extend(glob(expr))
        else:
            new_cmd.append(expr)
    globbed_commands.append(new_cmd)

You could do something horrendous like this:

globbed_commands = [
    [x for expr in cmd for x in (glob(expr) if "*" in expr or "?" in expr else [expr])]
    for cmd in commands
]

But that is getting extremely unweildy. It might become tolerable with a helper function:

def maybe_glob(expr):
    if "*" in expr or "?" in expr:
        return glob(expr)
    else:
        return [expr]


globbed_commands = [
    [x for expr in cmd for x in maybe_glob(expr)]
    for cmd in commands
]

But I would just go with the first for-loop I have above.

Related