## Pyscriptlib - Python Bash Script Helpers
### Whats new in version 2.3.1
- Added `shp(cmd, **kwargs)` to run a command in the background.
- Returns its `subprocess.Popen` object.
- Added `shbg(cmd, **kwargs)` to run a command in the background and read its stdout in a separate thread.
- This function creates a `BackgroundProcess(cmd, **kwargs)` class instance to manage the background process
- The `get_stdout(sentinel=None)` method will return immediately, while reading the stdout of the background process in a separate thread up to the sentinel string (if any).
- The `join_stdout(timeout=None)` method will wait up to `timeout` seconds for the IO thread to complete.
- It will also wait for the background process to complete if it is still running after `timeout` seconds.
- Returns: `stdout_str, stderr, returncode, pid`
- Improved `humanize(seconds, style='compact', days='days', day='day', *, zerodays=True)` to use `day` instead of `days` when style is compact and there is only 1 day.
- You can also change the language of `days=` and `day=` such as `days='días'` and `day='día'`.
- Added `class Shell` to manage the shell variables `shell` and `shell_bang`
- `Shell.shell` is the variable used to access the current shell value
- `Shell.shell = '/bin/bash'` is the default bash shell used by `sh*()` functions
### Introducing pyscriptlib
***pyscriptlib*** is a collection of helper functions that make it easy to invoke `bash` shell commands from a python script.
These helpers simplify the use of the `sys`, `os`, and `subprocess` modules for common cases.
From `sys` we get:
- `arg(n:int) -> str`
- a simple parse of `sys.argv` that always returns a string
- also consider `argparse` or `click-shell` if you are building a complex CLI
- `nargs(n:int) -> List(str)`
- retrieve the remaining command line args starting with `arg(n)`
- `shift(n:int=1) -> List[str]`
- shifts `sys.argv` by `n` preserving `sys.argv[0]` returning removed args
- `exit(return_code:int=0)`
- exeunt, pursued by a bear (WS)
From `os` we get:
- `env(var:str) -> str|None`
- easy retrieval of environment variables
- `kill(pid:int, signal:int=9)`
- this subprocess is killing me, so kill or be killed
From `subprocess` we get several useful variations of `sh*(cmd:str, **kwargs)` to invoke the `/bin/bash` shell for us (including `subprocess.Popen(**kwargs)` if necessary):
- `sh(cmd:str, **kwargs) -> (stdout+stderr).strip()`
- output, output, gimme output
- `shl(cmd:str, **kwargs) -> list(rc, stdout, stderr)`
- ok, ok, maybe we should take a look first: `if rc == 0`
- `shk(cmd:str, **kwargs) -> list(is_ok, rc, stdout, stderr)`
- let's test with a concise boolean `is_ok` instead of `rc == 0`
- `sho(cmd:str, **kwargs) -> CompletedProcess(is_ok, rc, stdout, stderr)`
- `cp = sho(cmd)` is the full Monty
- customized with `cp.is_ok` and `cp.rc`
- `shx(cmd:str, **kwargs) -> None`
- displays live output to terminal just like you want it to in living color
- `shb(cmd:str, **kwargs) -> int(pid)`
- runs `cmd` in background, returning immediately
- returns the pid
- you can `kill(pid)` if you are not a daemon
- `shp(cmd:str, **kwargs) -> Popen(pid, stdout, stderr)`
- runs `cmd` in background, returning immediately
- returns the `subprocess.Popen` object
- `shbg(cmd:str, **kwargs) -> BackgroundProcess(pid, stdout, stderr)`
- runs `cmd` in background, returning immediately
- returns a `BackgroundProcess` object with `get_stdout()` and `join_stdout()` methods
- usage:
```python
bg = shbg('echo -e "hello\nfrom\nbackground\nprocess"; sleep 3')
bg.get_stdout(sentinel='back')
print('waiting for output...')
stdout_str, stderr, rc, pid = bg.join_stdout(timeout=3)
print(stdout_str)
```
- `class Shell` can be used to select your preferred shell
- `Shell.shell` is the variable used to access the current shell value
- `Shell.shell = '/bin/bash'` is the default bash shell used by `sh*()` functions
And, from nowhere in particular, we get
- `humanize(seconds:int|float, style='compact', days='days', day='day', *, zerodays=True) -> str()`
- returns a human readable form of elapsed seconds such as `cat /proc/uptime`
- style = `full:` '05d 03h 59m 27s' or `compact:` '05 days 03:59:27'
- days = `days` or `day`
- zerodays = `True` or `False`
- `joinlines(lines:list) -> str`
- this is the inverse of `str.splitlines()`
- returns list as a string concatenated with LF's
- Constants: `SP`, `TAB2`, `TAB = TAB4`, `LF`, `CRLF`
- spaces and line feed constants useful in string formats
### Installation
***pyscriptlib*** is available at ***pypi.org***
```bash
pip install pyscriptlib
```
### Example Usage
See ***pyscriptlib/example_script.py*** below
```python
#! /usr/bin/env python3
'''
Usage: python3 /path/to/site-packages/pyscriptlib/example_script.py ~/some/example/dir arg2 arg3
'''
from pathlib import Path
import time
from pyscriptlib import (arg, nargs, shift, env,
sh, shx, shl, shk, sho, shb, shp, shbg,
kill, humanize)
def title(descr, code):
print(f'\n>>> {descr}\n>>> {code}')
title('Retrieve first sys.argv or os.environ variable MY_DIR_PATH or None if not present',
'dir_path = arg(1) or env("MY_DIR_PATH")')
dir_path = arg(1) or env('MY_DIR_PATH')
print(f'{dir_path = }')
title('Get remaining args',
'remaining_args = nargs(1)')
remaining_args = nargs(2)
print(f'{remaining_args = }')
title('Shift args',
'removed = shift()); arg1 = arg(1)')
removed = shift()
arg1 = arg(1)
print(f'{removed = } {arg1 = }')
title('Open the dir_path and verify it is a directory',
'if Path(dir_path).expanduser().is_dir() else exit(2)')
if dir_path:
my_dir = Path(dir_path).expanduser()
if not my_dir.is_dir():
print(f'Exiting: cannot find {dir_path}')
exit(2)
else:
print('usage: ./myscript dir_path')
print(' -- or -- ')
print(' MY_DIR_PATH=~/git/pyscriptlib; ./myscript')
exit(1)
print(f'{my_dir = }')
title('Capture the stripped output from stdout+stderr',
'output = sh(cmd)')
output = sh(f'ls -alh {my_dir}')
print(output)
title('Execute the command directly sending output to the terminal',
'shx(cmd)')
shx(f'tree {my_dir}')
title('Get a list of return values from subprocess.CompletedProcess object -- test with rc == 0',
'rc, stdout, stderr = shl(cmd); if rc == 0:')
rc, stdout, stderr = shl(f'ls -alh {my_dir}')
if rc == 0:
print(f'{rc = }\n{stdout = }')
else:
print(f'{rc = }\n{stderr = }')
title('Get a list of return values from subprocess.CompletedProcess object -- test with boolean is_ok',
'is_ok, rc, stdout, stderr = shk(cmd); if is_ok:')
is_ok, rc, stdout, stderr = shk(f'ls -alh {my_dir}')
if is_ok:
print(f'{is_ok = } {rc = }\n{stdout = }')
else:
print(f'{is_ok = } {rc = }\n{stderr = }')
title('Get the customized subprocess.CompletedProcess object -- test with boolean cp.is_ok',
'cp = sho(cmd); if cp.is_ok:')
cmd = f'''
cd {my_dir}
grep -r \
--exclude='*.pyc' \
--exclude-dir='.git' --exclude-dir='dist' --exclude-dir='*.egg-info' \
pyscriptlib
'''
print('cmd = ', cmd)
cp = sho(cmd)
if cp.is_ok:
text = cp.stdout.splitlines()
print(text)
else:
print(f'grep failed: {cp.rc = } {cp.stderr = }')
exit(cp.rc)
title('Create and kill background process with pid',
'pid = shb(cmd); kill(pid)')
pid = shb(f'echo "hello from background process"; sleep 10')
print(f'{pid = }')
time.sleep(1)
kill(pid)
title('Get the stdout from a completed background process',
'proc = shp(cmd); print(proc.stdout.read())')
proc = shp(f'echo "hello from background process"; sleep 3')
print(proc.stdout.read())
title('Get the stdout from a background process in a separate thread',
'bg = shbg(cmd); bg.get_stdout(sentinel="back"); stdout_str, stderr, rc, pid = bg.join_stdout(timeout=3)')
cmd = 'echo -e "hello\nfrom\nbackground\nprocess"; sleep 3'
print(f'{cmd = }')
bg = shbg(cmd)
bg.get_stdout(sentinel='back')
print('waiting for output...')
stdout_str, stderr, rc, pid = bg.join_stdout(timeout=3)
print(f'{stderr = } {rc = } {pid = }')
print(stdout_str)
title('Humanize the uptime for this host',
'uptimes = sh("cat /proc/uptime"); humanize(uptimes[0]))')
uptimes = sh('cat /proc/uptime')
if uptimes:
uptime = float(uptimes.split(' ')[0])
print(f'{humanize(uptime) = }')
print(f'{humanize(uptime, style="full") = }')
```
### A Personal Note
I've been using `bash` for over 35 years (since the days of Bell Labs Unix where it was invented by Stephen Bourne), but I never really felt very confident with its (to me) arcane syntax.
No, I don't mean just the things like this
```bash
# I'm not always sure which condition form to use
# and watch those spaces around the brackets!!!
# not to mention tests like -x, -n, -z and $? == 0 is success?
if <condition> | [ <condition> ] | [[ <condition> ]]
then
<statements>
else
<statements>
fi
# seriously, case );; );; esac anyone?
case <value> in
<match> ) <statements> ;;
<match> ) <statements> ;;
* ) <statements>;;
esac
```
But mostly the magic stuff you can do with array notation that is so cryptic it makes my eyes water.
```bash
# I'm still not sure what all this means :-(
locations=( "New York" Chicago Atlanta Miami )
for val in ${!locations[@]}
do
echo "index = ${val} , value = ${locations[$val]}"
done
```
Sigh, and I thought that `perl` was noisy ...
So I started looking for a better shell and what I actually found was ***python***. It's a more powerful interpreter with a much more comfortable and much less cryptic syntax than `bash`.
Along the way, I also considered the python `conch` shell, but it wants to be a new hybrid language in a REPL, and I don't really want the overhead of another layer of shell even if it is `ipython` at its heart.
In particular, all I really want to do is easily invoke the `bash shell` to run useful tools like `grep`, `ls -alh`, `tree`, `ip address`, *et alia*, but otherwise I'm happy with python syntax as the medium in a script file.
Unfortunately, the down-side to using python for scripting is that it is still a real programming language -- so creating `bash equivalent` scripts can get a bit hairy, to say the least.
This is especially true when you start using `subprocess.Popen()` and its incredibly powerful capabilities to fork any process you want with any options you want. But the price of power can be overwhealming complexity.
And so, I finally realized that all I really need is a concise set of wrapper functions that I can use to embed `bash commands` in my python code and let them actually do all the heavy lifting using `subprocess.Popen()` -- and why not throw in some `sys` and `os` sugar as well?
And, thus, complexity begat ***pyscriptlib*** and here we are.
### A Testimonial to pathlib.Path -- Can I have an Amen?
I am also highly impressed with the `Path` class from the standard `pathlib` module. The `Path` object model helps my python scripts become much more concise as I'm often navigating the file system such as `Path.home()/'subdir'` before I launch some `sh*(cmd)` that accesses the files.
Ooh, ooh, Mr. Kotter, Mr. Kotter: this is not a typo, rather, it is the coolest use of the class dunder method `__truediv__` to create a `Path slash(/) join operator` that I have seen:
```python
from pathlib import Path
# using the Path slash(/) join operator
file_path = Path.home() / 'subdir' / 'file'
# is the same as using the Path.joinpath() method
file_path = Path.home().joinpath('subdir', 'file')
# and the Path instance has simple direct access methods to the files
if file_path.is_file():
text = file_path.read_text()
# instead of more complexity using a context manager such as with open():
with open(file_path, 'r') as file:
text = file.read()
```
The old way of doing this with `os.path` was only slightly less painful than `subprocess.Popen`. I leave that as a heartless exercise for the reader :-).
Raw data
{
"_id": null,
"home_page": "https://gitlab.com/ntwrick/pyscriptlib",
"name": "pyscriptlib",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": null,
"keywords": "scripting, shell, bash, helpers, subprocess, sys, os",
"author": "Rick Arnold",
"author_email": "ntwrick@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/c5/6f/49ad0c890e229d23d2f927fb9387b4f4b21f49bc43669c1306ffc1849a9c/pyscriptlib-2.3.1.tar.gz",
"platform": null,
"description": "## Pyscriptlib - Python Bash Script Helpers\n### Whats new in version 2.3.1\n- Added `shp(cmd, **kwargs)` to run a command in the background.\n - Returns its `subprocess.Popen` object.\n- Added `shbg(cmd, **kwargs)` to run a command in the background and read its stdout in a separate thread.\n - This function creates a `BackgroundProcess(cmd, **kwargs)` class instance to manage the background process\n - The `get_stdout(sentinel=None)` method will return immediately, while reading the stdout of the background process in a separate thread up to the sentinel string (if any).\n - The `join_stdout(timeout=None)` method will wait up to `timeout` seconds for the IO thread to complete.\n - It will also wait for the background process to complete if it is still running after `timeout` seconds.\n - Returns: `stdout_str, stderr, returncode, pid`\n- Improved `humanize(seconds, style='compact', days='days', day='day', *, zerodays=True)` to use `day` instead of `days` when style is compact and there is only 1 day. \n - You can also change the language of `days=` and `day=` such as `days='d\u00edas'` and `day='d\u00eda'`.\n- Added `class Shell` to manage the shell variables `shell` and `shell_bang`\n - `Shell.shell` is the variable used to access the current shell value\n - `Shell.shell = '/bin/bash'` is the default bash shell used by `sh*()` functions\n\n### Introducing pyscriptlib\n***pyscriptlib*** is a collection of helper functions that make it easy to invoke `bash` shell commands from a python script.\n\nThese helpers simplify the use of the `sys`, `os`, and `subprocess` modules for common cases.\n\nFrom `sys` we get:\n- `arg(n:int) -> str`\n - a simple parse of `sys.argv` that always returns a string\n - also consider `argparse` or `click-shell` if you are building a complex CLI\n- `nargs(n:int) -> List(str)`\n - retrieve the remaining command line args starting with `arg(n)`\n- `shift(n:int=1) -> List[str]`\n - shifts `sys.argv` by `n` preserving `sys.argv[0]` returning removed args\n- `exit(return_code:int=0)`\n - exeunt, pursued by a bear (WS)\n\nFrom `os` we get:\n- `env(var:str) -> str|None`\n - easy retrieval of environment variables\n- `kill(pid:int, signal:int=9)`\n - this subprocess is killing me, so kill or be killed\n\nFrom `subprocess` we get several useful variations of `sh*(cmd:str, **kwargs)` to invoke the `/bin/bash` shell for us (including `subprocess.Popen(**kwargs)` if necessary):\n- `sh(cmd:str, **kwargs) -> (stdout+stderr).strip()`\n - output, output, gimme output\n- `shl(cmd:str, **kwargs) -> list(rc, stdout, stderr)`\n - ok, ok, maybe we should take a look first: `if rc == 0`\n- `shk(cmd:str, **kwargs) -> list(is_ok, rc, stdout, stderr)`\n - let's test with a concise boolean `is_ok` instead of `rc == 0`\n- `sho(cmd:str, **kwargs) -> CompletedProcess(is_ok, rc, stdout, stderr)`\n - `cp = sho(cmd)` is the full Monty\n - customized with `cp.is_ok` and `cp.rc` \n- `shx(cmd:str, **kwargs) -> None`\n - displays live output to terminal just like you want it to in living color\n- `shb(cmd:str, **kwargs) -> int(pid)`\n - runs `cmd` in background, returning immediately\n - returns the pid \n - you can `kill(pid)` if you are not a daemon\n- `shp(cmd:str, **kwargs) -> Popen(pid, stdout, stderr)`\n - runs `cmd` in background, returning immediately\n - returns the `subprocess.Popen` object\n- `shbg(cmd:str, **kwargs) -> BackgroundProcess(pid, stdout, stderr)`\n - runs `cmd` in background, returning immediately\n - returns a `BackgroundProcess` object with `get_stdout()` and `join_stdout()` methods\n - usage: \n ```python\n bg = shbg('echo -e \"hello\\nfrom\\nbackground\\nprocess\"; sleep 3')\n bg.get_stdout(sentinel='back')\n print('waiting for output...')\n stdout_str, stderr, rc, pid = bg.join_stdout(timeout=3)\n print(stdout_str)\n ```\n- `class Shell` can be used to select your preferred shell\n - `Shell.shell` is the variable used to access the current shell value\n - `Shell.shell = '/bin/bash'` is the default bash shell used by `sh*()` functions\n\nAnd, from nowhere in particular, we get\n- `humanize(seconds:int|float, style='compact', days='days', day='day', *, zerodays=True) -> str()`\n - returns a human readable form of elapsed seconds such as `cat /proc/uptime`\n - style = `full:` '05d 03h 59m 27s' or `compact:` '05 days 03:59:27'\n - days = `days` or `day`\n - zerodays = `True` or `False`\n- `joinlines(lines:list) -> str`\n - this is the inverse of `str.splitlines()`\n - returns list as a string concatenated with LF's\n- Constants: `SP`, `TAB2`, `TAB = TAB4`, `LF`, `CRLF`\n - spaces and line feed constants useful in string formats\n\n### Installation\n***pyscriptlib*** is available at ***pypi.org***\n```bash\npip install pyscriptlib\n```\n\n### Example Usage\nSee ***pyscriptlib/example_script.py*** below\n```python\n#! /usr/bin/env python3\n'''\nUsage: python3 /path/to/site-packages/pyscriptlib/example_script.py ~/some/example/dir arg2 arg3\n'''\n\n\nfrom pathlib import Path\nimport time\nfrom pyscriptlib import (arg, nargs, shift, env, \n sh, shx, shl, shk, sho, shb, shp, shbg,\n kill, humanize)\n\ndef title(descr, code):\n print(f'\\n>>> {descr}\\n>>> {code}')\n \n\ntitle('Retrieve first sys.argv or os.environ variable MY_DIR_PATH or None if not present',\n 'dir_path = arg(1) or env(\"MY_DIR_PATH\")')\ndir_path = arg(1) or env('MY_DIR_PATH')\nprint(f'{dir_path = }')\n\n\ntitle('Get remaining args',\n 'remaining_args = nargs(1)')\nremaining_args = nargs(2)\nprint(f'{remaining_args = }')\n\n\ntitle('Shift args',\n 'removed = shift()); arg1 = arg(1)')\nremoved = shift()\narg1 = arg(1)\nprint(f'{removed = } {arg1 = }')\n\n\ntitle('Open the dir_path and verify it is a directory',\n 'if Path(dir_path).expanduser().is_dir() else exit(2)')\nif dir_path:\n my_dir = Path(dir_path).expanduser()\n if not my_dir.is_dir():\n print(f'Exiting: cannot find {dir_path}')\n exit(2)\nelse:\n print('usage: ./myscript dir_path') \n print(' -- or -- ')\n print(' MY_DIR_PATH=~/git/pyscriptlib; ./myscript')\n exit(1)\nprint(f'{my_dir = }')\n\n\ntitle('Capture the stripped output from stdout+stderr',\n 'output = sh(cmd)')\noutput = sh(f'ls -alh {my_dir}')\nprint(output)\n\n\ntitle('Execute the command directly sending output to the terminal',\n 'shx(cmd)')\nshx(f'tree {my_dir}')\n\n\ntitle('Get a list of return values from subprocess.CompletedProcess object -- test with rc == 0',\n 'rc, stdout, stderr = shl(cmd); if rc == 0:')\nrc, stdout, stderr = shl(f'ls -alh {my_dir}')\nif rc == 0:\n print(f'{rc = }\\n{stdout = }')\nelse:\n print(f'{rc = }\\n{stderr = }')\n\n\ntitle('Get a list of return values from subprocess.CompletedProcess object -- test with boolean is_ok',\n 'is_ok, rc, stdout, stderr = shk(cmd); if is_ok:')\nis_ok, rc, stdout, stderr = shk(f'ls -alh {my_dir}')\nif is_ok:\n print(f'{is_ok = } {rc = }\\n{stdout = }')\nelse:\n print(f'{is_ok = } {rc = }\\n{stderr = }')\n\n\ntitle('Get the customized subprocess.CompletedProcess object -- test with boolean cp.is_ok',\n 'cp = sho(cmd); if cp.is_ok:')\ncmd = f'''\ncd {my_dir}\ngrep -r \\\n --exclude='*.pyc' \\\n --exclude-dir='.git' --exclude-dir='dist' --exclude-dir='*.egg-info' \\\n pyscriptlib\n'''\nprint('cmd = ', cmd)\ncp = sho(cmd)\nif cp.is_ok:\n text = cp.stdout.splitlines()\n print(text)\nelse:\n print(f'grep failed: {cp.rc = } {cp.stderr = }')\n exit(cp.rc)\n\n\ntitle('Create and kill background process with pid',\n 'pid = shb(cmd); kill(pid)')\npid = shb(f'echo \"hello from background process\"; sleep 10')\nprint(f'{pid = }')\ntime.sleep(1)\nkill(pid)\n\n\ntitle('Get the stdout from a completed background process',\n 'proc = shp(cmd); print(proc.stdout.read())')\nproc = shp(f'echo \"hello from background process\"; sleep 3')\nprint(proc.stdout.read()) \n\n\ntitle('Get the stdout from a background process in a separate thread',\n 'bg = shbg(cmd); bg.get_stdout(sentinel=\"back\"); stdout_str, stderr, rc, pid = bg.join_stdout(timeout=3)')\ncmd = 'echo -e \"hello\\nfrom\\nbackground\\nprocess\"; sleep 3'\nprint(f'{cmd = }')\nbg = shbg(cmd)\nbg.get_stdout(sentinel='back')\nprint('waiting for output...')\nstdout_str, stderr, rc, pid = bg.join_stdout(timeout=3)\nprint(f'{stderr = } {rc = } {pid = }')\nprint(stdout_str)\n\n\ntitle('Humanize the uptime for this host',\n 'uptimes = sh(\"cat /proc/uptime\"); humanize(uptimes[0]))')\nuptimes = sh('cat /proc/uptime')\nif uptimes:\n uptime = float(uptimes.split(' ')[0]) \n print(f'{humanize(uptime) = }')\n print(f'{humanize(uptime, style=\"full\") = }')\n```\n\n### A Personal Note\nI've been using `bash` for over 35 years (since the days of Bell Labs Unix where it was invented by Stephen Bourne), but I never really felt very confident with its (to me) arcane syntax. \n\nNo, I don't mean just the things like this\n```bash\n# I'm not always sure which condition form to use \n# and watch those spaces around the brackets!!! \n# not to mention tests like -x, -n, -z and $? == 0 is success?\nif <condition> | [ <condition> ] | [[ <condition> ]] \nthen\n <statements>\nelse \n <statements> \nfi\n\n# seriously, case );; );; esac anyone?\ncase <value> in \n <match> ) <statements> ;;\n <match> ) <statements> ;;\n * ) <statements>;;\nesac\n```\nBut mostly the magic stuff you can do with array notation that is so cryptic it makes my eyes water. \n\n```bash\n# I'm still not sure what all this means :-(\n\nlocations=( \"New York\" Chicago Atlanta Miami )\nfor val in ${!locations[@]}\ndo\n echo \"index = ${val} , value = ${locations[$val]}\"\ndone\n```\nSigh, and I thought that `perl` was noisy ...\n\nSo I started looking for a better shell and what I actually found was ***python***. It's a more powerful interpreter with a much more comfortable and much less cryptic syntax than `bash`. \n\nAlong the way, I also considered the python `conch` shell, but it wants to be a new hybrid language in a REPL, and I don't really want the overhead of another layer of shell even if it is `ipython` at its heart.\n\nIn particular, all I really want to do is easily invoke the `bash shell` to run useful tools like `grep`, `ls -alh`, `tree`, `ip address`, *et alia*, but otherwise I'm happy with python syntax as the medium in a script file.\n\nUnfortunately, the down-side to using python for scripting is that it is still a real programming language -- so creating `bash equivalent` scripts can get a bit hairy, to say the least.\n\nThis is especially true when you start using `subprocess.Popen()` and its incredibly powerful capabilities to fork any process you want with any options you want. But the price of power can be overwhealming complexity.\n\nAnd so, I finally realized that all I really need is a concise set of wrapper functions that I can use to embed `bash commands` in my python code and let them actually do all the heavy lifting using `subprocess.Popen()` -- and why not throw in some `sys` and `os` sugar as well? \n\nAnd, thus, complexity begat ***pyscriptlib*** and here we are.\n\n### A Testimonial to pathlib.Path -- Can I have an Amen?\nI am also highly impressed with the `Path` class from the standard `pathlib` module. The `Path` object model helps my python scripts become much more concise as I'm often navigating the file system such as `Path.home()/'subdir'` before I launch some `sh*(cmd)` that accesses the files. \n\nOoh, ooh, Mr. Kotter, Mr. Kotter: this is not a typo, rather, it is the coolest use of the class dunder method `__truediv__` to create a `Path slash(/) join operator` that I have seen:\n```python\nfrom pathlib import Path\n\n# using the Path slash(/) join operator\nfile_path = Path.home() / 'subdir' / 'file'\n\n# is the same as using the Path.joinpath() method\nfile_path = Path.home().joinpath('subdir', 'file')\n\n# and the Path instance has simple direct access methods to the files\nif file_path.is_file():\n text = file_path.read_text()\n\n# instead of more complexity using a context manager such as with open():\nwith open(file_path, 'r') as file:\n text = file.read()\n```\nThe old way of doing this with `os.path` was only slightly less painful than `subprocess.Popen`. I leave that as a heartless exercise for the reader :-).\n",
"bugtrack_url": null,
"license": null,
"summary": "Python Bash Script Helpers",
"version": "2.3.1",
"project_urls": {
"Homepage": "https://gitlab.com/ntwrick/pyscriptlib"
},
"split_keywords": [
"scripting",
" shell",
" bash",
" helpers",
" subprocess",
" sys",
" os"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "cf72db7a860341ad81856bf05edea3c8c5de1ff8fa5eae8e5e018de8911555aa",
"md5": "050c13ece5a8d445cc26d7670dcd5fbf",
"sha256": "2f4888f34185c47be9b9d35bc848f2c162105d96feecf2cac4c9dc4f4dc9479f"
},
"downloads": -1,
"filename": "pyscriptlib-2.3.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "050c13ece5a8d445cc26d7670dcd5fbf",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 11590,
"upload_time": "2024-09-20T22:16:42",
"upload_time_iso_8601": "2024-09-20T22:16:42.544928Z",
"url": "https://files.pythonhosted.org/packages/cf/72/db7a860341ad81856bf05edea3c8c5de1ff8fa5eae8e5e018de8911555aa/pyscriptlib-2.3.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "c56f49ad0c890e229d23d2f927fb9387b4f4b21f49bc43669c1306ffc1849a9c",
"md5": "256d865da8805ad8e5c11e87319a545d",
"sha256": "3ed12fec70b9b50178ccacc6f4fbf49fe7e68ec55346fe7887f7fe613161c9ee"
},
"downloads": -1,
"filename": "pyscriptlib-2.3.1.tar.gz",
"has_sig": false,
"md5_digest": "256d865da8805ad8e5c11e87319a545d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 13084,
"upload_time": "2024-09-20T22:16:43",
"upload_time_iso_8601": "2024-09-20T22:16:43.735742Z",
"url": "https://files.pythonhosted.org/packages/c5/6f/49ad0c890e229d23d2f927fb9387b4f4b21f49bc43669c1306ffc1849a9c/pyscriptlib-2.3.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-20 22:16:43",
"github": false,
"gitlab": true,
"bitbucket": false,
"codeberg": false,
"gitlab_user": "ntwrick",
"gitlab_project": "pyscriptlib",
"lcname": "pyscriptlib"
}