Check If an IP Is SSH’able

I often need to determine if I can ssh into an IP address. In other world, for the server identified by the IP address, is the SSH port (22) is open?

First Attempt: Use bash and ssh

In my first attempt, I used the ssh command from bash to check. This approach is not doable because I don’t know in advance the user name and password; all I know is the IP address.

Second Attempt: Use the socket Library

In this attempt, I use the socket library and attempt to connect to the IP address on port 22:

import socket


def sshable(ip_address, port=22):
    """
    Returns True if the IP address is ssh'able, False otherwise
    """
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        return_code = sock.connect_ex((ip_address, port))
    return return_code

Sample usage:

if not sshable("192.1.1.254"):
    print("Cannot ssh into address")

In the function above, note that I used the connect_ex method instead of connect. The difference is the former returns a none-zero error code upon failure; whereas the later will throw some exception.

Conclusion

Small functions like this is intuitive to look at, and if someone spend a few minutes to look up online would come up with it. The point here is to have it ready, when the need arise, I only need to copy it and use.

How to Parse /etc/os-release

Introduction

At work, I often need to parse contents of files such as /etc/os-release. Here is a sample of what the file looks like:

VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

In this post, I will discuss my different attempts and their pros and cons.

First Attempt: Split Line at the Equal Sign

This was the most obvious attempt, given a line such as:

line = 'VERSION="10 (buster)"'

We can split it by the equal sign:

line.split("=")
['VERSION', '"10 (buster)"']

The split results in 2 parts: the key (VERSION) and the value (“10 (buster)”). We are not done yet: we need to strip the quotes from the value. Putting it all together:

def parse_line(line):
    key, value = line.split("=")
    value = value.strip('"')
    return key, value

Test it out:

parse_line(line)
('VERSION', '10 (buster)')

Now that we successfully parse one line, we can parse multiple lines. Given that:

text = """VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
"""

We can write the parser which split the text into lines and parse each line:

def parse_etc_os_release(text):
    dict_object = dict(
        parse_line(line)
        for line in text.splitlines()
    )
    return dict_object

Test it out:

parse_etc_os_release(text)
{'VERSION': '10 (buster)',
 'VERSION_CODENAME': 'buster',
 'ID': 'raspbian',
 'ID_LIKE': 'debian',
 'HOME_URL': 'http://www.raspbian.org/',
 'SUPPORT_URL': 'http://www.raspbian.org/RaspbianForums',
 'BUG_REPORT_URL': 'http://www.raspbian.org/RaspbianBugs'}

This approach was obvious, does not use any standard- or third-party library, so I really like it. However, I usually challenge myself to find alternative solutions, so I looked around and found another solution.

Second Attempt: Use the csv Library

At closer look, the contents of /etc/os-release looks like a comma-separated-value (csv) file, only with the equal sign as separator. That means I could use the csv library to parse it:

import csv

def parse_etc_os_release2(text):
    # CSV can "sniff" or guess the separators
    dialect = csv.Sniffer().sniff(text)

    # Parse it and turn into a dictionary
    lines = text.splitlines()
    csv_reader = csv.reader(lines, dialect)
    dict_object = dict(csv_reader)

    return dict_object

Test it out:

parse_etc_os_release2(text)
{'VERSION': '10 (buster)',
 'VERSION_CODENAME': 'buster',
 'ID': 'raspbian',
 'ID_LIKE': 'debian',
 'HOME_URL': 'http://www.raspbian.org/',
 'SUPPORT_URL': 'http://www.raspbian.org/RaspbianForums',
 'BUG_REPORT_URL': 'http://www.raspbian.org/RaspbianBugs'}

In the code above, we first use the csv library to guess the dialect of the file, which includes separators, quote characters, and other characteristics. Next, we create a csv.reader object and use the dict construct to iterate over the lines and return a dictionary object.

The advantage of this method is the csv library can handle a wide variety of separators and quote characters. The disadvantage of this method, compare to the first one is the first does not need to use any library, standard or third party.

Conclusion

For parsing /etc/os-release alone, I believe the two methods are a tie. However, the second can parse more formats: imagine file formats which uses colon instead of equal sign as a separator.

Add Unique Directory to the PATH Environment Variable

The Problem

My .bashrc is full of lines like this one:

export PATH=$PATH:$HOME/bin

There are a couple of problems with this approach: First, the directory $HOME/bin might not exist (I share my .bashrc among my different computers so chances are not all of them have the same directory structure). Second, the directory might already be in the path.

What I need is a function which “adds the directory in question to the PATH if and only if a) the directory exists and b) if it is not already in the path. That brings me to write the bash function I called append_if_exists.

Continue reading

My BSD Experience

I have been using Linux for a long time. Before that, I used Unix and BSD at school. In my heart BSD is something I want to explore and turn into my daily driver.

That is why in 2011, I tried out FreeBSD, OpenBSD, NetBSD, then PC-BSD. Sadly, I kept running into problems which prevent me to use BSD for my daily tasks. Did I mention that I am a software engineer? That means the task of setting up and tweaking an operating system should not be too hard for me. However, for BSD, I gave up after a couple of days.

Continue reading

Synchronize Directories in Bash Shell

The Problem

I often work in a Linux environment with multiple windows open in a tmux session. One of the pattern I see often is the need to navigate to the same directory for multiple windows. For example, in window 1, I navigated to a directory deep within my project:

$ cd ~/long/path/to/my/directory

Then, on window 2, I want to navigate to the same directory. This often involves either copy the command from window 1 and paste into window 2, or retype the same command again. There must be a better way.

Continue reading

Customize Jupyter QtConsole Font

Introduction

I am using the Jupyter QtConsole all the time to try out Python ideas. One thing I noticed that on my Windows 10 system, the font looks beautiful whereas on my macOS, it looks quite ugly so I am setting out to fix it.

About Jupyter QtConsole

Jupyter QtConsole comes with Anaconda installation.

Before Customization

By default, the console uses the Monaco font, which I don’t like:
before

Customize it

From the terminal, I issued the following command:

jupyter qtconsole --generate-config

After that, a configuration file was created in ~/.jupyter/jupyter_qtconsole_config.py.bak. Next, I edited it using my favorite editor and made the following changes:

c.JupyterConsoleApp.confirm_exit = False
c.JupyterQtConsoleApp.display_banner = False
c.ConsoleWidget.console_height = 60
c.ConsoleWidget.console_width = 120
c.ConsoleWidget.font_family = 'Inconsolata'

The visual change comes from the last line. At this point, my console looks like this:

before

Overall, I like the font Inconsolata much better than Monaco.