6 votes

best way to go about with a script that seems to need both bash and python functionality

Gonna try and put this into words.

I am pretty familiar with bash and python. used both quite a bit and feel more or less comfortable with them.

My issue is I often do a thing where if I want to accomplish a task that is maybe a bit complex, I feel like I have to wind up making a script, let's call it hello_word.sh but then I also make a script called .hello_world.py

and basically what I do is almost the first line of the bash script, I call the python script like ./hello_world.py $@ and take advtange of the argparse library in python to determine what the user wants to do amongst other tasks that are easier to do in python like for loops and etc.

I try to do the meat of the logic in the python scripts before I write to an .env file from it and then in the bash script, I will do

set -o allexport
source "${DIR}"/"${ENV_FILE}"
set +o allexport

and then use the variable from that env file to do the rest of the logic in bash.

why do I do anything in bash?

cause I very much prefer being able to see a terminal command being executed in real-time and see what it does and be able to Ctrl+c if I see the command go awry.

in python, you can run a command with subprocess or other similar system libraries but you can't get the output in real-time or terminate a command preemptively and I really hate that. you have to wait for the command to end to see what happened.

But I feel like there is something obvious I am missing (like maybe bash has an argparse library I don't know about and there is some way to inject the concept of types into it) or if there is another language entirely that fits my needs?

11 comments

  1. [6]
    stu2b50
    Link
    I'm not sure what bash is getting you here. If all you want is to be able to run a subprocess and pipe it's output to stdout in realtime, all you need to do is poll the output in a loop.

    I'm not sure what bash is getting you here. If all you want is to be able to run a subprocess and pipe it's output to stdout in realtime, all you need to do is poll the output in a loop.

    10 votes
    1. [5]
      b3_k1nd_rw1nd
      Link Parent
      already answered this

      I'm not sure what bash is getting you here.

      already answered this

      be able to Ctrl+c if I see the command go awry.

      1 vote
      1. [4]
        stu2b50
        Link Parent
        Why would polling the subprocess not do the trick? A kill command to the python script will also kill its children.

        Why would polling the subprocess not do the trick? A kill command to the python script will also kill its children.

        10 votes
        1. [3]
          b3_k1nd_rw1nd
          Link Parent
          does polling a subprocess allow you to interact with that subprocess? like if it asks for input and that kind of thing?

          does polling a subprocess allow you to interact with that subprocess? like if it asks for input and that kind of thing?

          2 votes
          1. TangibleLight
            Link Parent
            Python gives you total control of all the input and output streams of all the subprocesses. It's a bit more verbose than Bash but I promise it's not missing any features. A demo of some PIPE...

            Python gives you total control of all the input and output streams of all the subprocesses. It's a bit more verbose than Bash but I promise it's not missing any features.

            A demo of some PIPE gymnastics in case it's helpful: Two subprocesses interacting with each other and Python code to glue them together.

            The first subprocess is a bash script that echoes lines from stdin with -I prepended. Python forwards stdin directly to this subprocess, but it intercepts stdout.

            Each of those outputs is passed as an argument to a new date process. The -I<format> prints the ISO timestamp to given precision date, hours, etc. Python again intercepts the output.

            If the date process succeeded, it URL-escapes the formatted date and prints it. If it failed (due to invalid argument), it prints the error message unchanged.

            Keyboard interrupt Ctrl+C works at any point. You can also send end-of-file Ctrl+D to end everything gracefully.

            Probably not the best way to solve this problem, but a demonstration of data weaving between processes without delays.
            import sys
            from subprocess import Popen, PIPE, run
            from urllib.parse import quote
            
            COMMAND = '''
            while read -r line; do
                echo "-I$line"
            done
            '''
            
            proc = Popen(
                ['bash', '-c', COMMAND],
                stdin=sys.stdin,
                stdout=PIPE,
                encoding='utf8',
            )
            
            for fmt in proc.stdout:
                result = run(
                    ['date', fmt.strip()],
                    capture_output=True,
                    encoding='utf8',
                )
                if not result.returncode:
                    print(quote(result.stdout.strip()))
                else:
                    print(result.stderr.strip())
            

            Some sample output:

            $ python script.py
            date
            2024-09-11
            huor
            date: invalid argument ‘huor’ for ‘--iso-8601’
            Valid arguments are:
              - ‘hours’
              - ‘minutes’
              - ‘date’
              - ‘seconds’
              - ‘ns’
            Try 'date --help' for more information.
            hour
            2024-09-11T22-04%3A00
            ^CTraceback (most recent call last):
              File "/home/allem/PycharmProjects/scratch/runner.py", line 20, in <module>
                for fmt in proc.stdout:
            KeyboardInterrupt
            

            Now obviously in practice this particular problem could be solved in a million easier ways - but I hope this convinces you that Python is not missing any features regarding piping data and handling interrupts. I haven't even touched import io or os.mkfifo which together generalize shell scripting concepts like process substitution and tee and similar.

            If Ctrl+C doesn't work for some reason, then some component somewhere is improperly intercepting those interrupts. It's probably not a Python (or bash) issue.

            8 votes
          2. stu2b50
            Link Parent
            That's up to what the python parent wants to do. You can detect particular prompts from stdout, and pass through user input when it happens on a case-by-case basis. For a more generic approach,...

            That's up to what the python parent wants to do. You can detect particular prompts from stdout, and pass through user input when it happens on a case-by-case basis.

            For a more generic approach, you'll have to use either threading or asyncio. With async, for instance, you can just wait on stdin and stdout on the subprocess in a loop.

            4 votes
  2. sparksbet
    Link
    You absolutely can Ctrl + C out of a running python script in the same way you can a bash script. The other reason you want to be using bash here, to "see the output of the program", confuses me a...

    You absolutely can Ctrl + C out of a running python script in the same way you can a bash script. The other reason you want to be using bash here, to "see the output of the program", confuses me a little -- do you just want it to log information to console while it's running? or is there some other bash-unique information you can get on a running script that you're really looking for here?

    7 votes
  3. [3]
    Eji1700
    Link
    I'm not that familiar with bash or python, but my world is dotnet, and you can absolutely do what functionality you describe with just C#/F#, and i'm positive its the same for python. This does...

    I'm not that familiar with bash or python, but my world is dotnet, and you can absolutely do what functionality you describe with just C#/F#, and i'm positive its the same for python.

    This does sound like one of those awkward problems where its all about wording the question properly though. To me it sounds like you just want the program to log to the console while its running while also retaining the ability to kill the process mid run correct?

    3 votes
    1. [2]
      b3_k1nd_rw1nd
      Link Parent
      Indeed, I had a hell of a time figuring out how to convert my needs to words for this post. And also to be able to interact with the bash command if it asks for input, yes.

      This does sound like one of those awkward problems where its all about wording the question properly though.

      Indeed, I had a hell of a time figuring out how to convert my needs to words for this post.

      To me it sounds like you just want the program to log to the console while its running while also retaining the ability to kill the process mid run correct?

      And also to be able to interact with the bash command if it asks for input, yes.

      1 vote
      1. Eji1700
        Link Parent
        Ok it's been forever since i've touched python, so i'm cheating a bit, but as an example of pseduocode, would this roughly look like the kind of execution you're expecitnng? import time...

        Ok it's been forever since i've touched python, so i'm cheating a bit, but as an example of pseduocode, would this roughly look like the kind of execution you're expecitnng?

        import time
        
        print("Enter a number and hit enter")
        
        entry = int(input())
        
        time.sleep(1)
        print("1")
        time.sleep(1)
        print("2")
        time.sleep(1)
        print("3")
        time.sleep(1)
        print("4")
        time.sleep(1)
        print("5")
        time.sleep(1)
        print(entry)
        
        print("Look a number, hit enter to close program")
        input()
        

        I can't mock it up right this second and run it, but this seems to:

        1. Accept entry from the user
        2. Simulate a longer process, thus giving time to Ctrl+C out
        3. Display the result.

        So maybe i'm misunderstanding the "interact with the bash command" part, but even then I feel like you could just do the command in python and have it pull input?

        2 votes