Python Sample Script

You inherit a folder full of bash scripts. They source each other, they call each other through pipelines, and after a while nobody remembers who depends on whom. You want a picture — “if I touch deploy.sh, what else might break?” — without reading every file by hand. This little Python script does exactly that: it scans a directory of .sh files, finds every reference one script makes to another, and renders the relationships as a GraphViz diagram. 🐍

What you’ll need

  • Python 3 — the code below runs as-is on a modern Python 3 (it was originally written for Python 2 but uses no incompatible idioms, so just python3 yourscript.py)
  • Graphviz — provides the dot command-line tool that turns DOT notation into an image. Install with sudo apt install graphviz (Debian/Ubuntu), sudo dnf install graphviz (Fedora/RHEL), or brew install graphviz (macOS).
  • An SVG viewer — the script ends by opening the result in eog (Eye of GNOME). On macOS, swap that for open; on Windows, start.

No third-party Python libraries are needed — everything (re, glob, ntpath, subprocess) is in the standard library.

Step 1: scan a single bash file for dependencies

The first job is to open one bash file and find every .sh filename mentioned inside it. We use a regex to grab anything that looks like a path ending in .sh, then walk backwards from each match to the start of the line to check whether the match is inside a comment or a quoted string — if it is, we skip it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import re
import ntpath
import glob
import subprocess

# In order to get this tool to run correctly, you'll need python installed (duh!) and graphviz.
basedir = "/home/ronald-dev/allBashScript/"

'''
This function will scan a bash file, and look for any dependency to another bash file
@param String    The fileName
@return Array    The dependency filenames
'''

def readDependencies(fileName):
    f = open(fileName, 'r')
    content = f.read()
    occurences = {}  # {file1.sh:1, file2.sh:1, file3.sh:1}
    for m in re.finditer('[\w|\/]*\.sh', content):
        word = ntpath.basename(content[m.start(): m.end()])
        if word not in occurences:
            isComment = False
            index = m.start()
            while index > 0 and content[index] != "\n":
                if content[index] == """ or content[index] == "'" or content[index] == "#":
                    isComment = True
                    break
                else:
                    index = index - 1
            if isComment == False:
                occurences[word] = 1
    f.close()
    return occurences.keys()

The dictionary trick (occurences[word] = 1) is just an old-school way of getting a unique set of names — every file only counts once, no matter how many times it’s referenced. Today you’d reach for a set() instead, but the result is the same. Change basedir to wherever your bash scripts live.

Step 2: turn dependencies into GraphViz DOT notation

GraphViz speaks a tiny language called DOT. A directed edge from a.sh to b.sh looks like “a.sh” -> “b.sh”;. Wrap a list of those in digraph G { … } and you have a complete graph. We need a function that turns one file’s dependencies into those edges, and a driver that does it for the whole directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
'''
Scan one bash file and convert its dependencies to GraphViz notation
@param String    The fileName
@param Array     The dependency filenames
@return String   GraphViz notation "bash01.sh" -> "bash02.sh";[NEWLINE] etc
'''

def toGraphvizNotation(fileName, arrayOfDependencies):
    string = ""
    filename = ntpath.basename(fileName)
    for dependency in arrayOfDependencies:
        string += '    "' + filename + '" -> "' + dependency + "";\n"
    return string

'''
Scans all bash files, with extension .sh
@param String    The directory to scan
@return Array    Array of filenames
'''
def readAllBashFiles(directory):
    return glob.glob(directory + "
*.sh")

'''
Scan multiple bash files and convert their dependencies to GraphViz notation
@param String    The directory to scan
@return String   GraphViz notation "
bash01.sh" -> "bash02.sh";[NEWLINE] etc
'''
def generateGraphVizFile(basedir):
    bashfiles = readAllBashFiles(basedir)
    graphVizFileContent = "
"
    for bashfile in bashfiles:
        graphVizFileContent += toGraphvizNotation(bashfile, readDependencies(bashfile))
    graphVizFileContent = "
digraph G { \n" + graphVizFileContent + "}"
    return graphVizFileContent

At this point we can turn the whole directory into a single block of DOT text — no files written yet, no images rendered, just a string that looks like:

1
2
3
4
5
digraph G {
    "deploy.sh" -> "common.sh";
    "deploy.sh" -> "db_backup.sh";
    "db_backup.sh" -> "common.sh";
}

Step 3: write the .dot file, render it, and open the picture

The last step has three parts: write the DOT string to a file, shell out to dot to convert it to SVG, then open the SVG in an image viewer. Three lines, one for each.

1
2
3
4
5
6
7
8
9
10
11
12
13
'''
Write content to a file
@param String    The filename to be written to
@param String    The content
'''

def writeGraphvizFile(filename, graphVizFileContent):
    f = open(filename, 'w')
    f.write(graphVizFileContent)
    f.close()

writeGraphvizFile("/tmp/bashdependencies.dot", generateGraphVizFile(basedir))
subprocess.call(["/usr/bin/dot", "-T", "svg", "-o", "/tmp/allBashScript.svg", "/tmp/bashdependencies.dot"])
subprocess.call(["eog", "/tmp/allBashScript.svg"])

Run the script and you should see your dependency graph open in an image viewer. If you’d rather just produce the SVG without opening it, drop the last subprocess.call line.


A few things worth knowing.

Python 3 vs Python 2. The original was written in 2014, when Python 2 was still everyone’s default. The functional code above runs unchanged on Python 3 — there are no print statements, no xrange, no implicit str/bytes mixing. Add a shebang and you’re done:

1
#!/usr/bin/env python3

The dot path is hard-coded. /usr/bin/dot works on most Linux distros but not on macOS (where Homebrew puts it under /opt/homebrew/bin/ on Apple Silicon, or /usr/local/bin/ on Intel) and not on Windows. Drop the absolute path and let $PATH resolve it:

1
subprocess.call(["dot", "-T", "svg", "-o", "/tmp/allBashScript.svg", "/tmp/bashdependencies.dot"])

Same idea for the viewer: eog is GNOME-only. Cross-platform, the standard trick is:

1
2
3
import sys
opener = {"darwin": "open", "win32": "start"}.get(sys.platform, "xdg-open")
subprocess.call([opener, "/tmp/allBashScript.svg"])

The regex has a small bug. The pattern [\w|\/]*\.sh uses a character class that includes | as a literal pipe — not as alternation, because | has no special meaning inside square brackets. It still works in practice (pipes rarely appear next to .sh), but the cleaner intent is [\w/]*\.sh.

The comment-detection misses quoted dependencies. The script walks backwards looking for #, , or to decide whether a match is inside a comment or string — and skips it if it is. That’s correct for # source helper.sh, but it also skips legitimate dependencies like source “common.sh” or bash ‘helper.sh’, because the trailing quote on the same line trips the check. If your scripts wrap filenames in quotes, you’ll get a graph that’s missing edges. A more robust approach is to strip comments first (everything after a non-quoted #), then run the regex on the cleaned content.

Modernise the file I/O. The pattern f = open(…); … ; f.close() works but won’t close the file if anything in between throws. Use with:

1
2
with open(fileName, 'r') as f:
    content = f.read()

And of course, in 2026 you might just ask a smart code-search tool for the same picture — but there’s still something satisfying about a script you can read end to end in five minutes. 🌳

Posted in Python | Comments Off on Python Sample Script

Enabling SSH (https) for Apache 2 in Ubuntu/Mint/Possibly other Debian distro

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Intall apache
$ sudo apt-get install apache2
 
# Enable SSL module
$ sudo a2enmod ssl

# Restart Apache
$ sudo service apache2 restart

# Create a directory to store the SSLCertificateFile and SSLCertificateKeyFile
$ mkdir  /etc/apache2/ssl

# Generate the keys
$ sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/apache2/ssl/apache.key -out /etc/apache2/ssl/apache.crt
Generating a 2048 bit RSA private key
...................................+++
................................+++
writing new private key to '/etc/apache2/ssl/apache.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CA
State or Province Name (full name) [Some-State]:Ontario
Locality Name (eg, city) []:Ottawa
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Ronald Pringadi      
Organizational Unit Name (eg, section) []:Engineering
Common Name (e.g. server FQDN or YOUR name) []:ronald-mint.com
Email Address []:webmaster@some-cool-website.com

# Edit your hosts file and add "127.0.0.1    some-cool-website.com"
$ gedit /etc/hosts


# Edit your sites-available ssl config
$ gedit /etc/apache2/sites-available/default-ssl.conf
#    Make sure you add the following line under the email
     ServerName some-cool-website:443
#    Also replace
     SSLCertificateFile /etc/apache2/ssl/apache.crt
     SSLCertificateKeyFile /etc/apache2/ssl/apache.key

# Activte site
$ sudo a2ensite default-ssl

# Reload Apache
$ sudo service apache2 reload

Open your browser and point it to https://some-cool-website.com. The browser will prompt you that the website is using a self-sign certificate, and do you want to continue and accept that certificate. Answer yes and that’s all to it

Posted in Linux, Ubuntu, Web Development | Comments Off on Enabling SSH (https) for Apache 2 in Ubuntu/Mint/Possibly other Debian distro

PHP and CURL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function browse($url, $postData = null) {
    $fields = '';
    if(is_array($postData) && sizeof($postData) >= 1){
        $fields = http_build_query($postData);
    }
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    if($fields !=='') {
        curl_setopt($ch, CURLOPT_POST, count($postData));
        curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
    }

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

    curl_setopt($ch, CURLOPT_COOKIESESSION, true);
    // Write cookie if needed  .. also try:  dirname(__FILE__) . '/cookie.txt';
    curl_setopt($ch, CURLOPT_COOKIEJAR , '/tmp/cookie.txt');
    // Read cookie if needed
    curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookie.txt');

    $result = curl_exec($ch);
    curl_close($ch);

    return $result;
}
Posted in PHP, Web Development | Comments Off on PHP and CURL

Directory sharing between Linux and Windows

1. Sharing/serving a Linux directory to Windows
$ sudo apt-get install samba
$ sudo smbpasswd -a USERNAME
$ mkdir /home/USERNAME/sharedfolder
$ sudo vi /etc/samba/smb.conf

[sharedfolder]
path = /home/USERNAME/sharedfolder
available = yes
valid users = USERNAME
read only = no
browsable = yes
public = yes
writable = yes

$ sudo restart smbd
Soruce: http://askubuntu.com/questions/19361/cant-access-ubuntus-shared-folders-from-windows-7

2. Linux Accessing Windows Shared Directory
sudo mount -t cifs //192.168.1.101/sharedirectoryabc -o username=YOURUSERNAME,password=YOURPASSWORD /home/userabc/sharedirectoryabc

Posted in Linux, Operating System, Windows 7 | Comments Off on Directory sharing between Linux and Windows

Choosing the default network card (NIC) that should access the Internet

In the presense of multiple network adapters, it is sometimes necessary to manually specify which one is the default used for internet routing, for example. To accomplish this, you have to manually add a “metric” to each interface. Windows will automatically use the interface with a lower metric. To check and change your network adapters’ metric:

1. Open Command Prompt and type: route print – you will see a list of active routes, the last column displaying their “metric”. Lower metric routes are preferred over higher ones.

2. Open the Network Adapter Properties (Control Panel > Network and Internet > Network Connections > right-click on adapter and choose Properties)
3. Open the properties of Internet Protocol Version 4 (TCP/IPv4).
4. Click on Advanced.
5. Untick “Automatic Metric” and set the interface metric to a number.
6. Hit OK until you close the Network Adapter properties.
7. Repeat steps 2-6 for your other network adapter(s) choosing different metrics. Remember lower metrics are preferred over higher ones.

Check the new metrics in Command Prompt by typing: route print

source: http://www.speedguide.net/faq_in_q.php?qid=350

Posted in Operating System, Windows 7 | Comments Off on Choosing the default network card (NIC) that should access the Internet

Getting the current filename using bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cat ./testfilename.sh
#!/bin/bash
fullfile=`basename $0`
filename=$(basename "$fullfile")
echo "filename:"$filename
extension="${filename##*.}"
echo "extension:"$extension
justfilename="${filename%.*}"
echo "justfilename:"$justfilename


$ ./testfilename.sh
filename:testfilename.sh
extension:sh
justfilename:testfilename
Posted in Linux, Operating System | Comments Off on Getting the current filename using bash

Customize your Vim editor using .vimrc

Quick notes:
– imap is for when you are in insert mode
– map is when your are in command mode

Thus when you are in the insert mode, sometime you need to exit to the command mode first using the esc key.
The i is for going back to the inser mode. The cr key is the Enter key.

The script below will make vim to:
– Find and Replace when I hit F3
– Undo when I hit F9
– Saves when I hit F10
– Quit when I hit F12
– Prints a debugging statement for PHP when I press F8
– Also disables the REPLACE mode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
""""""""""""""""""""""""""""""""""""""
" Quick commands using F keys
"
""""""""""""""""""""""""""""""""""""""
:imap <F3> <esc>:%s/search/replace/gc
:map <F3> :%s/search/replace/gc

:imap <F9> <esc>:u<cr>i
:map <F9> :u<cr>i

:imap <F10> <esc>:w!<cr>i
:map <F10> :w!<cr>i

:imap <F12> <esc>:q!<cr>
:map <F12> :q!<cr>

:imap <F8> error_log("\n".__FILE__ . ' ['. __LINE__. '] ' . __CLASS__.'::'.__FUNCTION__.'() D>'. print_r(, true));<cr>
:map <F8> ierror_log("\n".__FILE__ . ' ['. __LINE__. '] ' . __CLASS__.'::'.__FUNCTION__.'() D>'. print_r(, true));<cr>

"""""""""""""""""""""""""""""""""""""""
"
Disable REPLACE mode
"""""""""""""""""""""""""""""""""""""""
function s:ForbidReplace()
    if v:insertmode isnot# 'i'
        call feedkeys("
\<Insert>", "n")
    endif
endfunction
augroup ForbidReplaceMode
    autocmd!
    autocmd InsertEnter  * call s:ForbidReplace()
    autocmd InsertChange * call s:ForbidReplace()
augroup END
Posted in Linux | Comments Off on Customize your Vim editor using .vimrc

Search and replace withing a file

1
2
3
4
5
6
7
sed -i -e"s/SEARCH_TEXT/REPPLACE_TEXT/g" FILENAME.txt

# another example using bakup
sed -i.bak -e"s/SEARCH_TEXT/REPPLACE_TEXT/g" FILENAME.txt

# another example using variable
sed -i -e"s/\/pathname/\/pathname-$date/g" "filename-"$date".xml"
Posted in Bash, Linux, Operating System | Comments Off on Search and replace withing a file

Bash Scripting Basics (Variable Assignment, If, Loop)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
##################################################
# Benchmark the processing time when wkhtmltopdf
# converts an html file to a pdf file
##################################################

if [[ $1 = "" || $2 = "" || $3 = "" ]]; then
        echo "Usage: `basename $0` sourceFile.html destinationFile.pdf iteartionRunTime"
else
        _min=9999
        _max=0
        _total=0
        _iteration=$3
        for (( _i=1; _i<=$_iteration; _i++ ))
        do
                _start_time=`date +%s`
                echo "running $_i"
                ~MYUSER/bin/benchmarkWkhtmltopdf.sh $1 $2  > /dev/null 2>&1
                _end_time=`date +%s`
                _processing_time=$((_end_time-_start_time))
                if [ "$_processing_time" -lt "$_min" ]; then
                        _min=$_processing_time
                fi
                if [ "$_processing_time" -gt "$_max" ]; then
                        _max=$_processing_time
                fi
                _total=$(($_total+$_processing_time))
        done
       _processing_time_average=$(echo - | awk "{print $_total/$_iteration}")
        echo "Processing time avg: $_processing_time_average  min:$_min  max:$_max total:$_total"
fi
Posted in Bash, Linux | Comments Off on Bash Scripting Basics (Variable Assignment, If, Loop)

Compress and Extract Files in Linux

Extract:

.tar.bz2
To extract a tar (Tape ARchiver) file, type the following in the shell prompt:

1
tar xvjf yourFileName.tar.bz2

Which will untar it to the current directory. Depending on the file structure that being compressed, it might create sub-directories.
Parameters:
x – extract
v – verbose output (lists all files as they are extracted)
j – deal with bzipped file
f – read from a file, rather than a tape device

.tar.gz or .tgz

1
2
tar xvzf file.tar.gz
tar xvzf file.tgz

Parameters:
-x, –extract, –get extract files from an archive
-v, –verbose verbosely list files processed
-z, –gzip, –gunzip, –ungzip filter the archive through gzip
-f, –file=ARCHIVE use archive file or device ARCHIVE

.bz2 file

1
tar jxf YOUR_FILE_NAME.bz2

.zip file

1
unzip YOUR_FILE_NAME.zip
Posted in Bash, Linux | Tagged | Comments Off on Compress and Extract Files in Linux