7. Operating System

7.1. Accessing Environmental Variables

import os

os.getenv('HOME')  # /home/jose

7.2. Getting filenames and extensions

7.2.1. Extensions

import os

path, ext = os.path.splitext(r'c:\Python\README.rst')
path        # 'c:\\Python\\README'
ext         # '.rst'
import pathlib

pathlib.Path('my/library/setup.py').suffix   # '.py'
pathlib.Path('my/library.tar.gz').suffix     # '.gz'
pathlib.Path('my/library').suffix            # ''
pathlib.Path('my/library.tar.gar').suffixes  # ['.tar', '.gar']
pathlib.Path('my/library.tar.gz').suffixes   # ['.tar', '.gz']
pathlib.Path('my/library').suffixes          # []

7.2.2. Filenames

import pathlib

pathlib.Path('//some/share/setup.py').name  # 'setup.py'
pathlib.Path('//some/share').name           # ''
pathlib.Path('my/library.tar.gz').stem      # 'library.tar'
pathlib.Path('my/library.tar').stem         # 'library'
pathlib.Path('my/library').stem             # 'library'

7.3. Checking OS version

  • Linux: Linux
  • Mac: Darwin
  • Windows: Windows

7.3.1. platform

import platform

platform.system()                           # Windows
platform.release()                          # 7
platform.platform()                         # 'Windows-7-6.1.7601-SP1'
platform.os.name                            # 'nt'

platform.uname()
# uname_result(
#     system='Windows',
#     node='Lenovo-Komputer',
#     release='7',
#     version='6.1.7601',
#     machine='AMD64',
#     processor='Intel64 Family 6 Model 42 Stepping 7, GenuineIntel')
#
# uname_result(
#     system='Darwin',
#     node='mainframe.local',
#     release='15.3.0',
#     version='Darwin Kernel Version 15.3.0: Thu Dec 10 18:40:58 PST 2015; root:xnu-3248.30.4~1/RELEASE_X86_64',
#     machine='x86_64',
#     processor='i386')

7.3.2. os

import os

os.name         # 'nt'
os.name         # 'posix'

7.3.3. psutil

import psutil

psutil.OSX      # False
psutil.WINDOWS  # True
psutil.LINUX    # False

7.3.4. sys

import sys

sys.platform    # 'win32'

7.4. sys

7.4.1. Most commonly used methods

import sys

sys.path
sys.path.append
sys.platform
sys.path.insert(0, '/path/to/directory')
sys.path.insert(index=0, object='/path/to/directory')

7.4.2. System exit and exit codes

import sys

sys.exit(0)
Tab. 7.1. System Exit Codes
Code Description
1 Catchall for general errors
2 Misuse of shell builtins (according to Bash documentation)
126 Command invoked cannot execute
127 command not found
128 Invalid argument to exit
128+n Fatal error signal ‘n’
255 Exit status out of range (exit takes only integer args in the range 0 - 255)

7.5. os

import os

os.walk()
os.scandir()
os.getcwd()
os.stat()

os.is_dir()
os.is_file()
os.is_symlink()

os.path.join()
os.path.abspath()
os.path.dirname()
os.path.basename()

os.mkdir()
os.remove()
os.rmdir()
import os

os.path.isdir(os.path.join("c:", "\\", "Users"))    # True
os.path.isdir(os.path.join("c:", "/", "Users"))     # True
os.path.isdir(os.path.join("c:", os.sep, "Users"))  # True
import os

for element in os.scandir('/etc'):
    print(element.name)

script = os.path.basename(__file__)
PWD = os.path.basename(os.getcwd())

path = os.path.join(PWD, script)
print(path)
import os
from os.path import getsize


for root, dirs, files in os.walk('/home/'):
    size = sum(getsize(os.path.join(root, name)) for name in files)
    count = len(files)
    print(f'Size: {size} bytes in {count} non-directory files')

    # skip ``.git`` directories
    if '.git' in dirs:
        dirs.remove('.git')
# Delete everything reachable from the directory named in "top",
# assuming there are no symbolic links.
# CAUTION:  This is dangerous!  For example, if top == '/', it
# could delete all your disk files.
import os

for root, dirs, files in os.walk(top, topdown=False):

    for name in files:
        os.remove(os.path.join(root, name))

    for name in dirs:
        os.rmdir(os.path.join(root, name))

7.5.1. Stats and permissions

import os

output = os.stat(r'c:\Python\__notepad__.py')

print(output)
# os.stat_result(
#     st_mode=33206,
#     st_ino=3659174697409906,
#     st_dev=3763209288,
#     st_nlink=1,
#     st_uid=0,
#     st_gid=0,
#     st_size=780,
#     st_atime=1530775767,
#     st_mtime=1530775767,
#     st_ctime=1523261133)

oct(output.st_mode)
# 0o100666

7.5.2. Permissions

import os

os.access(r'C:\Python\README.rst', os.R_OK)     # True
os.access(r'C:\Python\README.rst', os.W_OK)     # True
os.access(r'C:\Python\README.rst', os.X_OK)     # True

os.access(r'C:\Python\notREADME.rst', os.R_OK)  # False
os.access(r'C:\Python\notREADME.rst', os.W_OK)  # False
os.access(r'C:\Python\notREADME.rst', os.X_OK)  # False

7.6. subprocess

7.6.1. Most commonly used methods

import subprocess

subprocess.call('clear')
subprocess.run()    # preferred over ``Popen()`` for Python >= 3.5
subprocess.Popen()

7.6.2. subprocess.run()

  • New in Python 3.5
  • Preferred
subprocess.run(
    args,
    stdin=None,
    stdout=None,
    stderr=None,
    shell=False,
    timeout=None,  # important
    check=False,
    encoding=None
    # ... there are other, less commonly used parameters
)

7.6.3. shell=True

Setting the shell argument to a true value causes subprocess to spawn an intermediate shell process, and tell it to run the command. In other words, using an intermediate shell means that variables, glob patterns, and other special shell features in the command string are processed before the command is run. Here, in the example, $HOME was processed before the echo command. Actually, this is the case of command with shell expansion while the command ls -l considered as a simple command.

Note

source: Subprocess Module <https://stackoverflow.com/a/36299483/228517>

import subprocess

subprocess.call('echo $HOME')
# OSError: [Errno 2] No such file or directory
import subprocess

subprocess.call('echo $HOME', shell=True)
# /home/jose-jimenez

7.6.4. Execute command in OS

subprocess.run('ls -la /home')  # without capturing output
import os
import subprocess

BASE_DIR = os.path.dirname(__file__)
path = os.path.join(BASE_DIR, 'README.rst')

subprocess.run(f'echo "ehlo world" > {my_path}')
import subprocess

cmd = 'dir ..'

output = subprocess.run(
    cmd,
    timeout=2,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    encoding='utf-8')

print(output.stdout)
print(output.stderr)
subprocess.run("exit 1", shell=True, check=True)
# subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE, encoding='utf-8')
# CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
#                  stdout='crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')

7.6.5. Timeout for subprocesses

import subprocess
cmd = ['ping', 'nasa.gov']

try:
    subprocess.run(cmd, timeout=5)
except subprocess.TimeoutExpired:
    print('process ran too long')

7.6.6. Stdout and Stderr

import logging
import subprocess
import shlex


def run(command, timeout=15, clear=True):

    if clear:
        subprocess.call('clear')

    logging.debug(f'Execute: {command}\n')

    result = subprocess.run(
        shlex.split(command),
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        shell=True,
        timeout=timeout,
        encoding='utf-8')

    if result.stdout:
        logging.info(f'{result.stdout}')

    if result.stderr:
        logging.warning(f'{result.stderr}')

    return result

7.6.7. Parsing and sanitizing arguments

import shlex
import subprocess

command_line = input()
# /bin/vikings -input eggs.txt -output "spam spam.txt" -cmd "echo '$MONEY'"

cmd = shlex.split(command_line)
# ['/bin/vikings', '-input', 'eggs.txt', '-output', 'spam spam.txt', '-cmd', "echo '$MONEY'"]

subprocess.run(cmd)
import subprocess
import shlex

cmd = 'dir ..'

output = subprocess.run(
    shlex.split(cmd),  # ['dir', '..']
    timeout=2,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    encoding='utf-8')

print(output.stdout)
print(output.stderr)

7.7. tempfile

7.7.1. Creating temporary files

import tempfile

with tempfile.TemporaryFile() as file:
    file.write(b'Hello world!')
    file.seek(0)
    file.read()  # b'Hello world!'

# file is now closed and removed

7.7.2. Creating temporary directories

with tempfile.TemporaryDirectory() as dir:
    print('created temporary directory', dir)

# directory and contents have been removed

7.8. io

  • io to biblioteka do obsługi strumienia wejściowego i wyjściowego
  • StringIO jest wtedy traktowany jak plik wejściowy.
import io

io.StringIO
io.BytesIO
f = open("myfile.txt", "r", encoding="utf-8")
f = io.StringIO("some initial text data")
f = open("myfile.jpg", "rb")
f = io.BytesIO(b"some initial binary data: \x00\x01")
import io

output = io.StringIO()
output.write('First line.\n')
print('Second line.', file=output)

# Retrieve file contents -- this will be
# 'First line.\nSecond line.\n'
contents = output.getvalue()

# Close object and discard memory buffer --
# .getvalue() will now raise an exception.
output.close()
b = io.BytesIO(b"abcdef")
view = b.getbuffer()
view[2:4] = b"56"
b.getvalue()  # b'ab56ef'

7.9. configparser

7.9.1. Writing configuration

import configparser

config = configparser.ConfigParser()

config['DEFAULT'] = {'ServerAliveInterval': '45',
                      'Compression': 'yes',
                      'CompressionLevel': '9'}

config['bitbucket.org'] = {}
config['bitbucket.org']['User'] = 'hg'
config['topsecret.server.com'] = {}

topsecret = config['topsecret.server.com']
topsecret['Port'] = '50022'
topsecret['ForwardX11'] = 'no'
config['DEFAULT']['ForwardX11'] = 'yes'

with open('example.ini', 'w') as configfile:
    config.write(configfile)
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes

[bitbucket.org]
User = hg

[topsecret.server.com]
Port = 50022
ForwardX11 = no

7.9.2. Reading configuration

import configparser

config = configparser.ConfigParser()

config.read('example.ini')          # ['example.ini']
config.sections()                   # ['bitbucket.org', 'topsecret.server.com']

'bitbucket.org' in config           # True
'example.com' in config             # False

config['bitbucket.org']['User']     # 'hg'
config['DEFAULT']['Compression']    # 'yes'

config.getboolean('BatchMode', fallback=True)        # True
config.getfloat('DEFAULT', 'a_float', fallback=0.0)  # 0.0
config.getint('DEFAULT', 'an_int', fallback=0)       # 0

topsecret = config['topsecret.server.com']
topsecret.get('ForwardX11', 'yes')          # 'no'
topsecret.get('Port', 8000)                 # '50022'


for key in config['bitbucket.org']:  # 'bitbucket.org' has laso entries from DEFAULT
    print(key)

    # user
    # compressionlevel
    # serveraliveinterval
    # compression
    # forwardx11

7.9.3. Alternative syntax and using variables in config

[Common]
home_dir: /Users
library_dir: /Library
system_dir: /System
macports_dir: /opt/local

[Frameworks]
Python: 3.2
path: ${Common:system_dir}/Library/Frameworks/

[Arthur]
nickname: Two Sheds
last_name: Jackson
my_dir: ${Common:home_dir}/twosheds
my_pictures: ${my_dir}/Pictures
python_dir: ${Frameworks:path}/Python/Versions/${Frameworks:Python}

7.10. pathlib

7.10.1. System os vs. pathlib

Tab. 7.2. System os vs. pathlib
os and os.path pathlib
os.path.abspath() Path.resolve()
os.getcwd() Path.cwd()
os.path.exists() Path.exists()
os.path.expanduser() Path.expanduser() and Path.home()
os.path.isdir() Path.is_dir()
os.path.isfile() Path.is_file()
os.path.islink() Path.is_symlink()
os.stat() Path.stat(), Path.owner(), Path.group()
os.path.isabs() PurePath.is_absolute()
os.path.join() PurePath.joinpath()
os.path.basename() PurePath.name
os.path.dirname() PurePath.parent
os.path.splitext() PurePath.suffix

7.10.2. .home()

import pathlib

pathlib.home()  # WindowsPath('C:/Users/José')

7.10.3. .drive

import pathlib

PureWindowsPath('c:/Program Files/').drive  # 'c:'
PureWindowsPath('/Program Files/').drive    # ''
PurePosixPath('/etc').drive                 # ''

7.10.4. .parents

import pathlib

p = PureWindowsPath('c:/foo/bar/setup.py')

p.parents[0]    # PureWindowsPath('c:/foo/bar')
p.parents[1]    # PureWindowsPath('c:/foo')
p.parents[2]    # PureWindowsPath('c:/')

7.10.5. .parent

import pathlib

p = PurePosixPath('/a/b/c/d')
p.parent        # PurePosixPath('/a/b/c')

7.10.6. .as_posix()

import pathlib

p = PureWindowsPath('c:\\windows')

str(p)          # 'c:\\windows'
p.as_posix()    # 'c:/windows'

7.10.7. .as_uri()

import pathlib

p = PurePosixPath('/etc/passwd')
p.as_uri()      # 'file:///etc/passwd'

p = PureWindowsPath('c:/Windows')
p.as_uri()      # 'file:///c:/Windows'

7.10.8. Path.chmod()

import pathlib

p = Path('setup.py')

oct(p.stat().st_mode)  # 0o100775
p.chmod(0o444)
oct(p.stat().st_mode)  # 0o100444

7.10.9. .glob()

import pathlib

sorted(Path('.').glob('*.py'))
# [PosixPath('pathlib.py'), PosixPath('setup.py'), PosixPath('test_pathlib.py')]

sorted(Path('.').glob('*/*.py'))
# [PosixPath('docs/conf.py')]

sorted(Path('.').glob('**/*.py'))
# [PosixPath('docs/conf.py'), ...]

7.10.10. .iterdir()

import pathlib

p = Path('docs')

for child in p.iterdir():
    print(child)

# PosixPath('docs/conf.py')
# PosixPath('docs/index.rst')
# PosixPath('docs/Makefile')
# PosixPath('docs/_build')
# PosixPath('docs/_static')
# PosixPath('docs/_templates')

7.11. Running commands in parallel across many hosts

../_images/system-pssh-1.jpg
../_images/system-pssh-2.jpg
../_images/system-pssh-3.png

7.12. Passwords and secrets

  • UMASK
  • Sticky bit
  • setuid
  • configparser

7.13. Allegro Tipboard

Tipboard is a system for creating dashboards, written in JavaScript and Python. Its widgets (‘tiles’ in Tipboard’s terminology) are completely separated from data sources, which provides great flexibility and relatively high degree of possible customizations.

Because of its intended target (displaying various data and statistics in your office), it is optimized for larger screens.

Similar projects: Geckoboard, Dashing.

$ pip install tipboard
$ tipboard create_project my_test_dashboard
$ tipboard runserver

7.14. Assignments

7.14.1. Recursive folders walking

  1. Sprawdź czy katalog “Python” już istnieje na pulpicie w Twoim systemie

  2. Jeżeli nie istnieje to za pomocą os.mkdir() stwórz go w tym miejscu

  3. Za pomocą subprocess.call() w tym katalogu stwórz plik README.rst i dodaj do niego tekst “Ehlo World”

  4. Przeszukaj rekurencyjnie wszystkie katalogi na pulpicie

  5. Znajdź wszystkie pliki README (z dowolnym rozszerzeniem)

  6. Wyświetl ich zawartość za pomocą polecenia:

    • cat (macOS, Linux)
    • type (Windows)
  7. Ścieżkę do powyższego pliku README skonstruuj za pomocą os.path.join()

  8. Ścieżka ma być względna w stosunku do pliku, który aktualnie jest uruchamiany

  9. Jeżeli po przeszukaniu całego Pulpitu rekurencyjnie skrypt nie znajdzie pliku LICENSE.rst, to ma rzucić informację logging.critical() i wyjść z kodem błędu 1.

About:
  • Filename: system_walk.py
  • Lines of code to write: 30 lines
  • Estimated time of completion: 30 min
Hints:
  • Gdyby był problem ze znalezieniem pliku, a ścieżka jest poprawna to zastosuj shell=True
  • os.walk()
  • subprocess.run()
Co to zadanie sprawdza?:
 
  • Przeglądanie katalogów i algorytm przeszukiwania
  • Sanityzacja parametrów
  • Logowanie wydarzeń w programie
  • Uruchamianie poleceń w systemie
  • Przechwytywanie outputu poleceń
  • Kody błędów
  • Przechodzenie do katalogów
  • Ścieżki względne i bezwzględne
  • Łączenie ścieżek

7.14.2. Tree

  1. Za pomocą znaków unicode: “┣━”, “┗━” , “┃ “
  2. Wygeneruj wynik przypominający wynik polecenia tree.
root:.
[.]
┣━[.idea]
┃  ┣━[scopes]
┃  ┃  ┗━scope_settings.xml
┃  ┣━.name
┃  ┣━Demo.iml
┃  ┣━encodings.xml
┃  ┣━misc.xml
┃  ┣━modules.xml
┃  ┣━vcs.xml
┃  ┗━workspace.xml
┣━[test1]
┃  ┗━test1.txt
┣━[test2]
┃  ┣━[test2-2]
┃  ┃  ┗━[test2-3]
┃  ┃      ┣━test2
┃  ┃      ┗━test2-3-1
┃  ┗━test2
┣━folder_tree_maker.py
┗━tree.py
About:
  • Filename: system_tree.py
  • Lines of code to write: 60 lines
  • Estimated time of completion: 60 min