Skip to content

Commit

Permalink
feat: chapter 30 - how to avoid shell scripting
Browse files Browse the repository at this point in the history
  • Loading branch information
dwmkerr authored Mar 7, 2022
1 parent 42b9946 commit 1c2b019
Show file tree
Hide file tree
Showing 9 changed files with 788 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ In this chapter we will be creating some files and folders, if you just want to

:::tip Downloading the Samples


Run the following command in your shell to download the samples:

```bash
Expand Down Expand Up @@ -477,4 +476,4 @@ In this chapter we looked at some sensible configuration settings for shells. We
In the next chapter we'll introduce Git - a version control tool we can use to manage changes to files like the 'dotfiles' easily over time. We can also use this tool to share our dotfiles across many machines.

[^1]: If you are curious, the `debian_chroot` variable is set when you are running as a user that has run the `chroot` (_change root_) command. The `chroot` command allows you to create an isolated file system tree. This lets you run programs in what is sometimes called a 'jail', which is a little like a container. `chroot` is an advanced topic and out of the scope of this book, but the `debian_chroot` command in the `PS1` variable is used to help make it clear when running a shell if you are in a 'changed root' environment.
[^2]: For a reminder on how to check whether a command is available, see _Checking for Installed Programs_ in [Chapter 23 - Useful Patterns for Shell Scripts](../../part-4-shell-scripting/useful-patterns-for-shell-scripts).
[^2]: For a reminder on how to check whether a command is available, see _Checking for Installed Programs_ in [Chapter 23 - Useful Patterns for Shell Scripts](../../part-4-shell-scripting/useful-patterns-for-shell-scripts).
446 changes: 446 additions & 0 deletions docs/06-advanced-techniques/30-how-to-avoid-scripting/index.md

Large diffs are not rendered by default.

25 changes: 0 additions & 25 deletions docs/xx-appendices/how-to-avoid-scripting.md

This file was deleted.

7 changes: 7 additions & 0 deletions samples/data/words.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
louche
liana
lieder
Manchu
Nankeen
naevi
Ness
26 changes: 26 additions & 0 deletions samples/programs/lookup/lookup-v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import sys

# Read standard input until there is nothing left to read.
while True:
# Read a line of input.
word = sys.stdin.readline()

# If the user hits 'Ctrl+D' to end transmission, readline returns an
# empty string and we can stop reading.
if not word:
break

# If the input is an empty line or whitespace, skip it.
if word.isspace():
continue

# Add the word to our list of lookups, and strip any whitespace from the
# beginning and end of it. For now, we don't have a definition.
word = word.strip()
definition = ''

# Write the result.
print("{} - {}".format(word, definition))

# Because we didn't actually define the words, exit with an error code.
sys.exit(1)
65 changes: 65 additions & 0 deletions samples/programs/lookup/lookup-v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import sys
import urllib.request
import urllib.parse
import json

ERROR_HTTP = 1
ERROR_PARSE = 2

def search_for_word(word):
# Encode the word for HTML.
encoded_word = urllib.parse.quote(word.encode('utf8'))

# Try and download the definition using the amazing dictionaryapi.dev site.
try:
url = "https://api.dictionaryapi.dev/api/v2/entries/en/{}".format(encoded_word)
response = urllib.request.urlopen(url)
if response.status == 404:
print("NOT FOUND")
sys.exit(1)
with urllib.request.urlopen(url) as response:
raw_json_data = response.read().decode('utf-8')
# If the word is not found, return an empty definition.
except urllib.error.HTTPError as http_error:
if http_error.code == 404:
return ''
raise
except Exception as e:
sys.stderr.write("An error occurred trying to download the definition of '{}'".format(word))
sys.exit(ERROR_HTTP)

# Now try and parse the data.
try:
data = json.loads(raw_json_data)
first_definition = data[0]['meanings'][0]['definitions'][0]['definition']
except Exception as e:
sys.stderr.write("An error occurred trying to parse the definition of '{}'".format(word))
sys.exit(ERROR_PARSE)

# Return the result.
return first_definition

# Read standard input until there is nothing left to read.
while True:
# Read a line of input.
word = sys.stdin.readline()

# If the user hits 'Ctrl+D' to end transmission, readline returns an
# empty string and we can stop reading.
if not word:
break

# If the input is an empty line or whitespace, skip it.
if word.isspace():
continue

# Strip whitespace from the word and find the definition.
word = word.strip()
stripped_word = word.strip()
definition = search_for_word(stripped_word)

# Write the result.
print("{} - {}".format(word, definition))

# Because we didn't actually define the words, exit with an error code.
sys.exit(1)
102 changes: 102 additions & 0 deletions samples/programs/lookup/lookup-v3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import sys
import argparse
import urllib.request
import urllib.parse
import json
import os

ERROR_HTTP = 1
ERROR_PARSE = 2

# Create an argument parser and define the arguments for our program.
parser = argparse.ArgumentParser()
parser.add_argument(
'-c', '--crop',
help='crop the output line length',
type=int,
nargs='?',
const=80, # Default value if -c is supplied
default=None) # Default value if -c is not supplied
args = parser.parse_args()

def search_for_word(word):
# Encode the word for HTML.
encoded_word = urllib.parse.quote(word.encode('utf8'))

# Try and download the definition using the amazing dictionaryapi.dev site.
try:
url = "https://api.dictionaryapi.dev/api/v2/entries/en/{}".format(encoded_word)
response = urllib.request.urlopen(url)
if response.status == 404:
print("NOT FOUND")
sys.exit(1)
with urllib.request.urlopen(url) as response:
raw_json_data = response.read().decode('utf-8')
# If the word is not found, return an empty definition.
except urllib.error.HTTPError as http_error:
if http_error.code == 404:
return ''
raise
except Exception as e:
sys.stderr.write("An error occurred trying to download the definition of '{}'".format(word))
sys.exit(ERROR_HTTP)

# Now try and parse the data.
try:
data = json.loads(raw_json_data)
first_definition = data[0]['meanings'][0]['definitions'][0]['definition']
except Exception as e:
sys.stderr.write("An error occurred trying to parse the definition of '{}'".format(word))
sys.exit(ERROR_PARSE)

# Return the result.
return first_definition

def write_definition(word, definition):
# Check if stdout is a terminal - if it is we'll colour the output.
is_terminal = sys.stdout.isatty()

# We will separate the word and the definition with a colon and space.
separator = ": "

# If the 'crop' argument is set, use it.
if args.crop:
output_length = len(word) + len(separator) + len(definition)
if output_length > args.crop:
# We need to chop some letters off the end of the definition, but
# leave space for '...' to indicate the output is cropped.
new_length = len(definition) - 3 - (output_length - args.crop)
definition = definition[:new_length] + '...'

# If we are in a terminal make the word green and the separator white.
if is_terminal:
word = "\033[92m" + word + "\033[0m"
separator = "\033[37m" + separator + "\033[0m"

# Write out the word, separator and definition.
print(word + separator + definition)

# Read standard input until there is nothing left to read.
while True:
# Read a line of input.
word = sys.stdin.readline()

# If the user hits 'Ctrl+D' to end transmission, readline returns an
# empty string and we can stop reading.
if not word:
break

# If the input is an empty line or whitespace, skip it.
if word.isspace():
continue

# Strip whitespace from the word and find the definition.
word = word.strip()
stripped_word = word.strip()
definition = search_for_word(stripped_word)

# Write the result.
write_definition(word, definition)

# Because we didn't actually define the words, exit with an error code.
sys.exit(1)
140 changes: 140 additions & 0 deletions samples/programs/lookup/lookup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/usr/bin/env python3

import sys
import argparse
import urllib.request
import urllib.parse
import json
import os

ERROR_INTERRUPT = 1
ERROR_HTTP = 2
ERROR_PARSE = 3

examples = '''example:
lookup philosophy
lookup --crop -- metonymy metaphor simile
cat list-of-words.txt | lookup > defintions.txt'''

# Create an argument parser and define the arguments for our program.
parser = argparse.ArgumentParser(
prog='parser',
description='lookup the defintion of words from dictionaryapi.dev',
epilog=examples,
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'words',
help='optional list of words to define - if omitted stdin is used',
type=str,
nargs='*'
)
parser.add_argument(
'-c', '--crop',
help='crop the output line length',
type=int,
nargs='?',
const=80, # Default value if -c is supplied
default=None # Default value if -c is not supplied
)
args = parser.parse_args()

def search_for_word(word):
# Encode the word for HTML.
encoded_word = urllib.parse.quote(word.encode('utf8'))

# Try and download the definition using the amazing dictionaryapi.dev site.
try:
url = "https://api.dictionaryapi.dev/api/v2/entries/en/{}".format(encoded_word)
response = urllib.request.urlopen(url)
if response.status == 404:
print("NOT FOUND")
sys.exit(1)
with urllib.request.urlopen(url) as response:
raw_json_data = response.read().decode('utf-8')
# If the word is not found, return an empty definition.
except urllib.error.HTTPError as http_error:
if http_error.code == 404:
return ''
raise
# If the user hits ctrl hit, exit without an error message.
except KeyboardInterrupt:
sys.exit(ERROR_INTERRUPT)
except Exception as e:
sys.stderr.write("An error occurred trying to download the definition of '{}'".format(word))
sys.exit(ERROR_HTTP)

# Now try and parse the data.
try:
data = json.loads(raw_json_data)
first_definition = data[0]['meanings'][0]['definitions'][0]['definition']
# If the user hits ctrl hit, exit without an error message.
except KeyboardInterrupt:
sys.exit(ERROR_INTERRUPT)
except Exception as e:
sys.stderr.write("An error occurred trying to parse the definition of '{}'".format(word))
sys.exit(ERROR_PARSE)

# Return the result.
return first_definition

def write_definition(word, definition):
# Check if stdout is a terminal - if it is we'll colour the output.
is_terminal = sys.stdout.isatty()

# We will separate the word and the definition with a colon and space.
separator = ": "

# If the 'crop' argument is set, use it.
if args.crop:
output_length = len(word) + len(separator) + len(definition)
if output_length > args.crop:
# We need to chop some letters off the end of the definition, but
# leave space for '...' to indicate the output is cropped.
new_length = len(definition) - 3 - (output_length - args.crop)
definition = definition[:new_length] + '...'

# If we are in a terminal make the word green and the separator white.
if is_terminal:
word = "\033[92m" + word + "\033[0m"
separator = "\033[37m" + separator + "\033[0m"

# Write out the word, separator and definition.
print(word + separator + definition)

# Get the next word to read - either from the arguments or stdin.
def read_words():
if args.words:
for word in args.words:
yield word
else:
while True:
try:
yield sys.stdin.readline()
# If the user hits ctrl hit, exit without an error message.
except KeyboardInterrupt:
sys.exit(ERROR_INTERRUPT)


# Process words until we run out of the provided list or end standard input.
for word in read_words():
# If the user hits 'Ctrl+D' to end transmission, readline returns an
# empty string and we can stop reading.
if not word:
break

# If the input is an empty line or whitespace, skip it.
if word.isspace():
continue

# Strip whitespace from the word and find the definition.
word = word.strip()
stripped_word = word.strip()
definition = search_for_word(stripped_word)

# Write the result.
write_definition(word, definition)

# Because we didn't actually define the words, exit with an error code.
sys.exit(1)
1 change: 1 addition & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const sidebars = {
link: { type: 'doc', id: 'advanced-techniques/index'},
items: [
'advanced-techniques/understanding-shell-expansion/index',
'advanced-techniques/how-to-avoid-scripting/index',
]
},
],
Expand Down

0 comments on commit 1c2b019

Please sign in to comment.