diff --git a/_posts/2025-02-12-python-script-output-redirection.md b/_posts/2025-02-12-python-script-output-redirection.md new file mode 100644 index 0000000..715a01b --- /dev/null +++ b/_posts/2025-02-12-python-script-output-redirection.md @@ -0,0 +1,121 @@ +--- +layout: post +title: "Python Script Output Redirection" +description: Integrate Python scripts into the Unix philosophy +tags: + - Python + - Unix +image: + path: /assets/img/posts/python-script-output-redirection/bash-shell.png + alt: A screenshot showing a python script appended with `> output.log 2>&1` which saves all output to a file. +--- + +> "Write programs to handle text streams, because that is a universal interface." +> +> -- Doug McIlroy, the inventor of Unix pipes [(source)](http://www.catb.org/~esr/writings/taoup/html/ch01s06.html#:~:text=Write%20programs%20to%20handle%20text%20streams%2C%20because%20that%20is%20a%20universal%20interface.) + +Today, for the Nth time I got stuck while trying to redirect a Python script's output to a file for later processing. So, after figuring it out again, I'm finally documenting my findings. + +Let's explore the default behavior for how Python scripts output to `stdout` vs `stderr`, and thus how we can save that output to a file or pipe it to another program (i.e. following the [Unix Philosophy](http://www.catb.org/~esr/writings/taoup/html/ch01s06.html)) + +## Example Script + +Here's a script that outputs a text using both the basic `print` command and the built-in `logging` module. + +```python +import logging +logging.basicConfig(level=logging.INFO, format="%(message)s") + +print("printed message") +logging.info("log message") +``` +{: file='script.py' } + +As expected, running the script outputs each line to a terminal. + +```bash +$ python script.py +printed message +log message +``` + +## Python sends `print` output to `stdout` + +Redirecting the scripts `stdout` only captures the `print` message, leaving the `log` messages in the terminal. + +```bash +$ python script.py > stdout.log +log message + +$ cat stdout.log +printed message +``` + +## Python sends `logging` output to `stderr` + +Redirecting `stderr` does the reverse, capturing the `log` output and leaving the `print` messages in the terminal. + +```bash +$ python script.py 2> stderr.log +printed message + +$ cat stderr.log +log message +``` + +## How to redirect `print` and `logging` output to the same file + +Both `stdout` and `stderr` can be redirected to the same file by _appending_ the command with `2>&1` (see [I/O redirection docs](https://tldp.org/LDP/abs/html/io-redirection.html#:~:text=2%3E%261%0A%20%20%20%20%20%20%23%20Redirects%20stderr%20to%20stdout.)). + +```bash +$ python script.py > output.log 2>&1 + +$ cat output.log +log message +printed message +``` + +> I'm not sure why the `print` output appears _after_ the `logging` output, when the script executes `print` _before_ `logging`. If you know why, please comment below! +{: .prompt-warning } + +## Piping output to another command + +Another key principle in Unix-land is that the output of one program can be the input to another program. Connecting one program's output to another program's input is done with the pipe character `|` and is sometimes called "piping". + +We'll start by using a command called `grep` which filters its input to only output the lines which contain a certain string. + +> In my shell, `grep` highlights the found string, which I will indicate here by adding \*stars\* +{: .prompt-info } + + +### Default Pipe Behavior + +```bash +$ python script.py | grep message +log message +printed *message* +``` + +Notice that only the `print` message was highlighted. By default, the pipe only redirects `stdout`, so `grep` only processed the `print` output. The `logging` messages went to `stderr` and thus were still output to the terminal even though they weren't processed by `grep`. + +### Piping `stdout` and `stderr` + +To send all output to `grep` we can use the same `2>&1` syntax from before. + +```bash +$ python script.py 2>&1 | grep message +log *message* +printed *message* +``` + +Now `grep` is processing every line. + +### Piping only `stderr` +If you want `grep` to only process the `stderr` output (i.e. `log` outputs), we have to send the `stdout` somewhere else. On Linux we can choose `/dev/null` which is a text trashcan. + +```bash +$ (python script.py 2>&1 1>/dev/null) | grep message +log *message* +``` + +Notice that the `print` output was discarded, while everything else was processed by `grep`. \ No newline at end of file diff --git a/assets/img/posts/python-script-output-redirection/bash-shell.png b/assets/img/posts/python-script-output-redirection/bash-shell.png new file mode 100644 index 0000000000000000000000000000000000000000..160525ec4875c4a2267c448bb38140710f248962 GIT binary patch literal 23212 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%0DJ= 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