7
\$\begingroup\$

My .bash_aliases file contains several aliases. In the beginning, when I wanted to see the aliases, I print the .bash_aliases file,

The output wasn't readable at all, so I created a python script to make it better. To call my python script, I use the alias print_aliases_manual

What is the most pythonic way to write the following script?

Here is my python script:


class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'


with open('/home/pi/.bash_aliases', 'r') as f:
    lines = f.readlines()

    for line in lines:
        if r'##' in line:
            print(f'{bcolors.HEADER}{bcolors.UNDERLINE}{bcolors.BOLD}{line}{bcolors.ENDC}')
        elif r'#' in line:
            print(f'{bcolors.WARNING}{line}{bcolors.ENDC}')
        elif r'alias' in line:
            drop_alias = line.replace('alias', '')
            new_line = drop_alias.replace('=', '   ')
            print(f'{bcolors.OKGREEN}{new_line}')
        else:
            print(f'{line}')

Here is my bash_aliases file:

### Aliases list divided by category:

## General commands:
alias cdd="cd /home/pi/Desktop/speedboat/"
alias dc="sudo docker-compose"
alias pm2="sudo pm2"

## Cellular related commands:
alias pc="ping -I wwan0 www.google.com"
alias qmi="sudo qmicli -d /dev/cdc-wdm0 -p"

## AWS relaeted commands:
alias dlogs="sudo docker logs -f iot_service"

## Git commands:
alias gp="sudo git pull"
alias gc="sudo git checkout"

## scripts to run when deleting speedboat directory is needed:

# copying files to Desktop
alias stodtop="sudo -E sh /home/pi/Desktop/speedboat/scripts/copy_files_to_desktop.sh"
# copying files from Desktop
alias sfromdtop="sudo -E sh /home/pi/Desktop/speedboat/scripts/copy_files_from_desktop_to_speedboat.sh"

## verifications test and updates scripts
# run speedboat verifications tests
alias sinsta="sudo sh /home/pi/Desktop/speedboat/scripts/installation_verification.sh"
# run local unittests
alias slocalt="sudo sh /home/pi/Desktop/speedboat/scripts/run_local_unittest.sh"
# pull os updates from git and execute
alias supdate="sudo sh /home/pi/Desktop/speedboat/scripts/updates-speedboat.sh"

## speedboat start and stop scripts (dockers and PM2)
alias startr="sudo sh /home/pi/Desktop/speedboat/scripts/start_speedboat.sh"

alias stopr="sudo sh /home/pi/Desktop/speedboat/scripts/stop_speedboat.sh"

## AWS related scripts:

alias secrl="sudo sh /home/pi/Desktop/speedboat/scripts/ecr-login.sh"

alias slog="sudo sh /home/pi/Desktop/speedboat/scripts/log_register.sh"

## Command to run speedboat aliases manual:
alias print_aliases_manual="python3 /home/pi/Desktop/speedboat/verification/test_verification_speedboat/colored_aliases_manual.py"



\$\endgroup\$

2 Answers 2

10
\$\begingroup\$

print

f-Strings are a great and convenient tool for including variables and calculations in strings. I'd say they're generally not needed when printing only variables. For example

print(f'{bcolors.WARNING}{line}{bcolors.ENDC}')

could become

print(bcolors.WARNING, line, bcolors.ENDC, sep='')

However I'm not sure which of the two is more pythonic, I personally prefer the latter for readability.

You could also consider pulling out formatting into seperate variables to increase readability.

HEADER_FORMAT = "".join((bcolors.HEADER, bcolors.UNDERLINE, bcolors.BOLD, "{}", bcolors.ENDC))

print(HEADER_FORMAT.format(line))

in vs. startswith

I'd recommend using str.startswith instead of str in str for checking prefixes.

if r'##' in line:

becomes

if line.startswith(r"##"):

This is less error-prone and conveys your intentions more explicitly, without the need to look at the source file. As there aren't any escape characters in your prefixes, you also don't need to make it a raw string. You can simply drop the r:

if line.startswith("##"):
\$\endgroup\$
2
  • 2
    \$\begingroup\$ @Reinderien Maybe I‘m looking at the wrong line here, but the second print has its seperator set to an empty string via the sep argument. \$\endgroup\$ Commented May 25, 2021 at 7:36
  • 1
    \$\begingroup\$ You're right, my apologies \$\endgroup\$
    – Reinderien
    Commented May 25, 2021 at 13:01
8
\$\begingroup\$

First, a laundry list of things:

  • bcolors is not only colours; "underline" and "end code" are not colours. Call this perhaps ANSICodes, title-case to be PEP8-compliant.
  • 'r' is the default file mode and can be omitted
  • Do not hard-code your home directory
  • You don't need readlines; iterate over the file object
  • As @riskypenguin suggests, in is not the right tool here. Consider startswith for your header and comments, and a regex for the aliases.
  • Pre-binding to format calls will clean things up a little
  • Try piping your output through less and observe the fireworks. You need to be assured that your stdout is a real TTY.
  • Consider making a fixed field width for your alias keys instead of four absolute spaces

Example

Note that I'm using a different file path from you.

import re
from os import getenv
from pathlib import Path
from sys import stdout
from typing import Callable

# Terrible workaround for Pycharm
DECORATE = stdout.isatty() or getenv('PYCHARM_HOSTED')

RE_ALIAS = re.compile(r'^alias (.+)="(.+)"')


class ANSICodes:
    RED = '\033[91m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    BLUE = '\033[94m'
    PINK = '\033[95m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'
    ENDC = '\033[0m'

    @classmethod
    def decorate(cls, *codes: str) -> Callable[[str], str]:
        return ''.join((
            *codes, '{}', cls.ENDC,
        )).format


header1 = ANSICodes.decorate(ANSICodes.PINK, ANSICodes.UNDERLINE, ANSICodes.BOLD)
comment = ANSICodes.decorate(ANSICodes.YELLOW)
alias = ANSICodes.decorate(ANSICodes.GREEN)


def transform(line: str) -> str:
    match = RE_ALIAS.match(line)
    if match:
        k, v = match.groups()
        line = f'{k:7} {v}'
        if DECORATE:
            return alias(line)
        return line

    if DECORATE:
        if line.startswith('##'):
            return header1(line)
        if line.startswith('#'):
            return comment(line)

    return line


def main():
    path = Path('.bash_aliases')
    # path = Path('~/.bash_aliases').expanduser()
    with path.open() as f:
        for line in f:
            print(transform(line.rstrip()))


if __name__ == '__main__':
    main()
\$\endgroup\$
2
  • 1
    \$\begingroup\$ THX!!! That is genuinely beneficial. @Reinderien, can you please explain how to pipe the stdout throw less? \$\endgroup\$
    – Monty_emma
    Commented May 25, 2021 at 4:04
  • 3
    \$\begingroup\$ @Monty_emma Instead of print_aliases_manual, you'd have print_aliases_manual | less. The | is a pipe. \$\endgroup\$
    – Teepeemm
    Commented May 25, 2021 at 12:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.