Computing Resources
Table of Contents
- 1. Software Engineering Practices
- 1.1. Make programs act consistently
- 1.2. Add context to your source code changes
- 1.3. Document each interface you affect
- 1.4. Use automated tools to reduce inconsistency
- 1.5. Make fixer and linter programs more likely to do the same thing on more computers
- 1.6. Try to turn Incomplete Lines into Lines
- 1.7. Avoid including
<tab>or any other control characters in files - 1.8. Avoid interacting with a global state unless that's necessary
- 1.9. Avoid repeating yourself
- 1.10. Use as few characters as possible to make your program match its documentation
- 1.11. Make each
gitcommit have as high code coverage using local testing as is reasonable - 1.12. Use a larger number of lines if it's possible to format them consistently
- 1.13. Use
nulland"y"to represent boolean values with strings - 1.14. Make comments easy to maintain
- 1.15. Prefer to quote even when it's not necessary
- 1.16. Follow other style guides and advice
- 1.17. Reduce the amount of Shell Command Language source code by replacing it with other programs
- 1.18. Prefer to avoid using Python
- 1.19. See also
- 2. General resources
- 3. Extensions
1. Software Engineering Practices
1.1. Make programs act consistently
1.1.1. Shell script maintainability and reliability
- Introduction
The Shell Command Language is very liberal: altering source code in small ways will likely not cause an interpreter to produce different results, in most circumstances, but if things outside the programs (such as file contents or execution environment) vary, some programs will produce undesirable results while other program's behavior will continue to produce results you like. For example, these commands write different numbers of lines:
statement="hello world" && printf '%s\n' ${statement} statement="hello world" && printf '%s\n' "${statement}"If your program expects both of those commands to write only one line, you will be surprised by the results, despite the only difference being some
<space>( ) characters are changed into quote (") characters!Because small changes in programs and inputs can produce severely negative results, great care should be taken to account for various types of reasonably likely situations
- Common inconsistencies
- Parameter expansion without braces
The
$character can introduce parameter expansion.Sometimes you want text to immediately follow the substituted value. For example, you may want to append
_2to the name of a file to make a backup of it, but doing this without braces produces results you wouldn't like:file=test.txt && touch -- "$file" && cp -- "$file" "$file_2" file=test.txt && touch -- "${file}" && cp -- "${file}" "${file}_2"The first command will likely fail while the second command succeeds, as variables that are unset often expand to zero characters, and "If the parameter is not enclosed in braces, and is a name, the expansion shall use the longest valid name […], whether or not the variable represented by that name exists."
shellcheckdocuments that it can automatically fix this problem by running this command with a value likesrc/my_file.shassigned tofile:{ shellcheck -x -o all -i SC2250 -f diff -- "./${file}" > shellcheck_diff || [ "${?}" = 1 ]; } && patch -p1 <shellcheck_diff - Allowing Field Splitting
Generally, text that is not
<space>or<tab>is passed to a command as an argument, and the number of arguments is determined by how many sequences of characters that are not<space>or<tab>appear. For example, this command has three arguments:ls first second third
However, a command can have additional arguments due to Field Splitting (substantially similar processes are "word splitting" and "word expansion"). This often happens when an expression or substitution caused new white space characters to be on the command line, and it results in the expansion being interpreted as a list of "fields" that undergo further expansion.
For example, the
lscommand on each of these lines will have six arguments:mkdir -- test && cd -- test && touch -- a b c && unset -v -- IFS && three_fields="a b c" && ls ${three_fields} ${three_fields} mkdir -- test && cd -- test && touch -- a b c && unset -v -- IFS && star="*" && ls ${star} ${star} mkdir -- test && cd -- test && touch -- a b c && IFS="," && a_comma_b_comma_c="a,b,c" && ls ${a_comma_b_comma_c} ${a_comma_b_comma_c} mkdir -- test && cd -- test && touch -- a b c && IFS="," && star_comma_star="*,*" && ls ${star_comma_star} mkdir -- test && cd -- test && touch -- a b c && unset -v -- IFS && star_comma_star="*,*" && ls ${star_comma_star} ${star_comma_star} ${star_comma_star} ${star_comma_star} ${star_comma_star} ${star_comma_star}Unfortunately, field splitting often produces unexpected results (as you might have guessed from the examples), and it changes its behavior in reaction to the environment in cases that aren't commonly checked for.
Because field splitting often leads to unexpected behavior, it should often be avoided by surrounding expansions and substitutions with double quote (
") characters, as "the shell shall scan the results of expansions and substitutions that did not occur in double-quotes for field splitting".These commands will write different things, as the first does not handle the value of
troublesome_texthaving spaces so writes many lines, whereas the second avoids field splitting so only prints one line that doesn't contain "bad":unset -v -- IFS && troublesome_text="bad -E" && printf '%s\n' "${troublesome_text}" "good text" > text.txt && opts="-v -e ${troublesome_text}" && grep -x ${opts} -- text.txt unset -v -- IFS && troublesome_text="bad -E" && printf '%s\n' "${troublesome_text}" "good text" > text.txt && should_invert_match="y" && grep -x ${should_invert_match:+"-v"} -e "${troublesome_text}" -- text.txtEven if field splitting isn't depended upon, it is still good to avoid by adding double-quotes: even if the program won't change its behavior in most cases due to lack of quoting, leaving a variable expansion unquoted makes it unclear whether you actually intended for field splitting to happen occasionally, and it may happen when you don't intend it to. For example, these commands write different things because the first allows field splitting of one expansion but the second doesn't use field splitting at all:
unset -v -- IFS && troublesome_text="bad -E" && printf '%s\n' "${troublesome_text}" "good text" > text.txt && should_invert_match="y" && grep -x ${should_invert_match:+"-v"} -e ${troublesome_text} -- text.txt unset -v -- IFS && troublesome_text="bad -E" && printf '%s\n' "${troublesome_text}" "good text" > text.txt && should_invert_match="y" && grep -x ${should_invert_match:+"-v"} -e "${troublesome_text}" -- text.txt - Not providing context for unusual paths
Sometimes, people will make poor decisions about what to start their directory entries with. If they use a dash (
-) as the first character of a name, that will often cause problems, as it could be interpreted as specifying one or more options instead of a file. For example, a file named-rwould often be confusing to usermwith.To enable more types of commands to be run, many commands support the "end of options delimiter" (
--) (described in the Utility Syntax Guidelines as linked to from various utility pages, to be recognized by default, named in a description ofeval). For example, the first of these commands will not write anything as-x(the value ofmy_file) will be interpreted as specifying thatgrepshould match only input lines where all characters in the line match, and the second command will match twice (one line in the file named-xand another inother_file.txt):my_file="-x" && printf '%s\n' "foo" "bar" > "${my_file}" && printf '%s\n' "baz" "qux" > other_file.txt && grep -e 'a' "${my_file}" other_file.txt my_file="-x" && printf '%s\n' "foo" "bar" > "${my_file}" && printf '%s\n' "baz" "qux" > other_file.txt && grep -e 'a' -- "${my_file}" other_file.txtUsing the end of options delimiter makes it clear to everyone what you intend to be an option (with or without an option-argument) (like
-e pattern) and what you intend to be an operand (likeother_file.txt).Additionally, you should make sure every path string starts with a character other than
-by checking whether they start with/and prepending./if not, to make sure the path is either explicitly absolute or clearly relative. This may sometimes be the only method available to get the behavior you want, especially to if a version offindinterprets a file name as part of the "expression" (as both are operands and operands starting with "-" or that are "!" or "(" are treated differently from others). For example, the first of these commands has written a different number of lines when compared to the second:my_file="-print" && mkdir -p -- "${my_file}" && touch -- "${my_file}"/a "${my_file}"/b "${my_file}"/c && find -- "${my_file}" -type f my_file="-print" && { [ -z "${my_file##/*}" ] || my_file="./${my_file}"; } && mkdir -p -- "${my_file}" && touch -- "${my_file}"/a "${my_file}"/b "${my_file}"/c && find -- "${my_file}" -type fMaking sure paths start with a safe character and using the end of options delimiter can be done at the same time, and doing both of these things is probably preferable to only doing one in case a mistake is made in implementing one of the practices.
- Not constructing commands such that they fail safely
Commands can fail for unpredictable reasons, like being killed by a signal or having too many variables passed between environments. For example, one of these comands has failed in a similar context to a time when the other has succeeded:
( seq_numbers="$(seq 100000)" && for i in ${seq_numbers}; do eval 'export var_'"${i}"'=0'; done; ls; ) ( seq_numbers="$(seq 10000)" && for i in ${seq_numbers}; do eval 'export var_'"${i}"'=0'; done; ls; )Because failure is possible and reasonably likely, commands should be constructed with the assumption that any command can fail unexpectedly, such that "the system's design prevents or mitigates unsafe consequences of the system's failure."
For example, one of these commands fails to detect problems with a path due to it not being a valid directory entry but the other handles the fact the file is nonexistent gracefully by reacting the same way to
gziporawkexiting with a nonzero status (by changing!=to==in theawkprogram to make it fail in an undesirable situation and swapping the "then compound-list" and "else compound-list"):set -o pipefail && file="nonexistent" && [ ! -e "${file}" ] && check_gzip_file() { if gzip -l -- "${1}" | awk 'NR==2 {exit($2!=0)}'; then printf '%s\n' the file may be empty and unsafe to use && false; else printf '%s\n' "the file may be populated"; fi; } && if check_gzip_file "${file}"; then printf '%s\n' "the file passed the check"; else printf '%s\n' "the file failed the check"; fi set -o pipefail && file="nonexistent" && [ ! -e "${file}" ] && check_gzip_file() { if gzip -l -- "${1}" | awk 'NR==2 {exit($2==0)}'; then printf '%s\n' "the file may be populated"; else printf '%s\n' the file may be empty and unsafe to use && false; fi; } && if check_gzip_file "${file}"; then printf '%s\n' "the file passed the check"; else printf '%s\n' "the file failed the check"; fiEssentially, when the exit status of a command is being checked, you should only assume the command succeeded if its exit status is exactly 0, and ensure that it is not likely to be 0 if something unexpected happened. Also, every
ifstatement should have an associatedelseword since if it doesn't it can be replaced with an AND-OR list to better express what your expectations for the command are (though usingifwith an appropriateelselist is often preferred over using an AND-OR list, and storing exit status in variables over checking them directly or indirectly is likely preferred), and "In some cases, status values are listed more loosely, such as >0. A strictly conforming application shall not rely on any specific value in the range shown and shall be prepared to receive any value in the range."You can construct a list to react to a specific exit value if that is necessary. For example, grep exits with status 1 if no lines were selected but a different nonzero status if an error occured, so checking for an exit status of 1 differentiates a non-match from both a match and a problem that prevented a full search. This command succeeds because the exit status of
grepis mapped to the typical "0 is good, anything else is bad" space by[:printf '%s\n' "hello" >test.txt && ( set +e && { grep -e 'goodbye' -- test.txt; [ "${?}" = 1 ]; }; ) - Not using variable names that include lowercase letters
This implies that any variable name that does not contain lowercase leters can modify the behavior of the standard utilities.
Note that a system may provide non-standard extensions, so variable names that are not mentioned in existing documentation could be made to affect standard utilities in the future. Consider this example, where
shcould be a different shell:unset -v -- SHLVL && SHLVL=400 && export -- SHLVL && sh -c -- 'printf "%s\n" "${SHLVL}"' - Not making a non-zero exit status of a compound-list available when it is used with a compound command
It can be difficult to tell if a compound-list used with a compound command has a non-zero exit status, since the exit status of the compound command can often be 0 (if no compound-list was executed) or the status of the last compound-list that was executed. This means that if no compound-list was executed, it's not necessarily clear whether no compound-list was executed or if the final compound-list to be executed had an exit status of 0, and if more than one compound-list was executed it's not necessarily clear what the exit status of any other compound-list was (or whether another one was executed at all).
This is not true of grouping commands since the exit status of a grouping command shall be the exit status of its only compound-list and it is always executed.
In many cases, making
exitbe executed as the final command of the compound-list if the most recently executed command had a non-zero exit status and makingprintf '%s\n' "${?}"be executed just before that will at least record the exit status or exit the program (and return the exit status of theprintfcommand if it had a non-zero exit status), so more details will be made available for debugging purposes. This might require changing a single AND-OR list into a sequence of lists, and doing that while retaining all the useful properties of the original AND-OR list might require a lot of thought.- Examples
for path in / /dev/null ~; do [ -d "${path}" ]; done for path in / /dev/null ~; do { { [ -d "${path}" ]; }; printf '%s\n' "${?}"; } || exit; donesudo -- systemctl restart monero && sudo -- systemctl stop monero && sudo -- nixos-rebuild switch --upgrade && reboot if sudo -- systemctl restart monero; then if sudo -- systemctl stop monero; then if sudo -- nixos-rebuild switch --upgrade; then reboot; else printf '%s\n' "${?} nixos-rebuild" || exit; fi; else printf '%s\n' "${?} systemctl stop" || exit; fi; else printf '%s\n' "${?} systemctl restart" || exit; fi
- Examples
- Not using
untilinstead ofwhile
The exit status of a
whileloop only depends on onecompound-listused with it, so it isn't very clear how the exit value of the other can be recorded. On the contrary, if the exit status of anuntilloop is0it is clear that each compound-list used with it succeeded if it was executed at all, and by using the?parameter with the first command of the second compound-list enclosed in the loop it can be determined what the exit status of the other compound-list was. - Using the
<<-redirection operator instead of the<<redirection operator
A here-document is often useful for embedding lines you want to use with the input of a command into a script file. However, it is unclear if using
<<-should result in leading<space>characters being stripped, as that behavior is not well documented with POSIX or bash, but that often happens anyway. Fixer programs often change any leading<tab>characters into leading sequences of<space>characters, but sometimes don't undo that when converting the file to use<tab>characters for indentation. This leads to the fixing process producing more changes than is necessary, so the<<-redirection operator should be replaced with the<<redirection operator where possible.For example, make source code more like the first of these snippets than the second:
cat <<"END" hello END
cat <<-"END" hello END
- Parameter expansion without braces
- Examples of non-obvious assumptions in documentation
Nobody should be able to create or rename entries in a directory your program knows about: https://www.gnu.org/software/findutils/manual/html_mono/find.html#A-more-secure-version-of-_002dexec
File names and paths should be short: file names must be as short as 14 characters and paths must be as short as 255 characters: "If any pathname component is longer than
{NAME_MAX}, the implementation shall consider this an error", "In the cases where prefixing occurs, if the combined length exceeds{PATH_MAX}, and the implementation considers this to be an error, pathname resolution shall fail" ({NAME_MAX}and{PATH_MAX}defined by https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html and given more context by https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pathchk.html)File names should not contain a
<space>( ) and should be restricted to a small set of characters: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_08 (which justifies the limited functionality of find and xargs compared to the GNU versions). - See also
A condensed explanation of how software like Unix is designed and how it affects the process of making a user program (its wikipedia page)
Analyses of how software development works in the Unix ecosystem (especially The Art of Unix Programming and The Cathedral and the Bazaar (the main text))
shellcheck git repository (also links to the project's website which has a wiki)
some pages which may be of interest for people doing Unix shell scripting
Commentary on how to handle input with links to other resources
POSIX.1-2017 defines a standard operating system interface and environment
Linux Foundation Referenced Specifications (a more reliable link to The Single UNIX® Specification, Version 2)
Bash Reference Manual (and other manuals by project)
ALE for automated linting with the
vimtext editor that works well with the shell command language and other languages
1.2. Add context to your source code changes
Add log messages to collections of changes to source files such that each collection requires less energy to find and/or understand than if it did not have a description. Use words, phrases, sentences, and capitalization that you expect people who want to find your collection of changes to search for after having only minimal exposure to your changes (for example, by reviewing a change or using git-blame), unless doing so makes a collection of changes take more energy to understand.
1.2.1. See also
- https://google.github.io/eng-practices/
- https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines
- https://git-scm.com/docs/git-commit
- https://github.blog/2022-06-30-write-better-commits-build-better-projects/
- https://www.conventionalcommits.org
- https://initialcommit.com/blog/git-commit-messages-best-practices
- https://wiki.openstack.org/wiki/GitCommitMessages
- https://who-t.blogspot.com/2009/12/on-commit-messages.html
- https://cbea.ms/git-commit/
- https://dev.to/luchfilip/making-git-commit-messages-useful-with-jira-jenkins-and-scopes-5759
- https://support.atlassian.com/jira-cloud-administration/docs/use-the-github-for-jira-app/
- https://www.freecodecamp.org/news/how-to-write-better-git-commit-messages/
1.3. Document each interface you affect
If you change a program such that it can act differently than it did in the past, provide to users of that program a notification of how to bring about the new behavior, any method that will bring about the old behavior, and what the effect(s) of the new behavior should be (by comparing each effect of the new behavior to that of the old behavior). This is probably best done by ensuring that a file named README.md is distributed with the source code for the program and that the content has a similar degree of helpfulness as what you see after executing man man.
A major reason for this is to make it clear what functionality is depended upon: documenting what commitments are needed for other parts of a program will clarify whether a change will cause harm or not.
1.4. Use automated tools to reduce inconsistency
Frequently use programs to automatically fix and reformat files. Change files if that makes relevant linter programs do fewer interesting things. Doing these things will likely reveal subtle issues by making changes related to them produce large git commits, and may also reduce the frequency of git merge conflicts and often make it clearer how to resolve them.
For examples of fixer and linter programs, see shfmt and shellcheck and https://github.com/dense-analysis/ale/tree/48d73c87c3c321e6298755abc5449267192d07e6/autoload/ale/fix/registry.vim and https://github.com/dense-analysis/ale/tree/48d73c87c3c321e6298755abc5449267192d07e6/autoload/ale/fixers and https://github.com/dense-analysis/ale/tree/48d73c87c3c321e6298755abc5449267192d07e6/ale_linters.
Try to make any fixer program you use make as few changes as possible compared to the source code most recently used to affect a production environment, by configuring it to change what it will write. For example, if making the program use four spaces for indentation instead of a tab character results in fewer changes, try to make it do that.
Use any fixer programs in the same order every time: it may be impossible to make two fixer programs write the same lines, and they may react to each other's changes such that they write things they wouldn't otherwise, so one should be preferred over another in order to avoid unnecessary deletions or additions. For example, fixing with IntelliJ then Eclipse will likely give different results than fixing with Eclipse then IntelliJ. Using fewer fixer programs will probably not be harmful, but order should be preserved. For example, formatting with only yapf then black instead of ruff then yapf then black is probably okay, though using ruff then black or ruff then yapf may result in more changes than is necessary.
Also consider that after you've made two commits, your commits could end up being blamed for every single line for a file, and it's probably better to have a message like "Format" displayed to people more often than "Refactor" is, so your first step before introducing any "real" change should probably be to make a commit that is only the result of using some fixer program with each file you're interested in.
1.5. Make fixer and linter programs more likely to do the same thing on more computers
Make it so that people more frequently use the same configuration with fixer and linter programs, probably by committing configuration files with git and/or making it clear what expectations are placed on changes to a repository (like with https://github.com/rust-lang/rustup/blob/4149df64251e674058ebc6e5a6e6cc07b07938cd/README.md#contributing or https://github.com/rust-lang/mdBook/blob/ec996d350955a8974f8cae5a55c1374ae8da4fac/CONTRIBUTING.md#code-quality). Of course, needing fewer changes from the default configuration makes it easier to have the same configuration everywhere.
1.6. Try to turn Incomplete Lines into Lines
Check whether your files end with a <newline> character, and consider adding one to the end if one doesn't. Having a file with an Incomplete Line is annoying, as it often gets automatically changed when editing a file, and many utilities do not have documented behavior with an Incomplete Line and exhibit surprising behavior when used with one (like wc).
1.7. Avoid including <tab> or any other control characters in files
Having leading tab characters on lines is natural indentation, and tabs shall be set a default of 8 column positions apart, but that often causes problems when using the clipboard to copy and paste. Typing a <tab> character with a shell is often interpreted as an instruction to attempt to perform completion on the text, so trying to paste text including a <tab> character may cause different text to be written than what one wants, which is often distressing. Other "control characters" will also likely cause distress for others at some point. Because of this, prefer to remove literal <tab> characters and any other White Space that isn't a <space> or <newline> and any other Control Character (see https://en.wikipedia.org/wiki/Control_character and https://www.ascii-code.com/characters/control-characters and https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap06.html and https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_112) from files and to indent only with spaces with source code that has any chance of being re-used verbatim.
1.8. Avoid interacting with a global state unless that's necessary
Every way a program interacts with a global state implicitly becomes an interface that a user or software engineer might end up depending on. You probably don't want to maintain that interface, but if it exists people will probably use it so you will probably have to end up maintaining it.
Some examples of interacting with a global state:
- The Shell Command Language
- Using a variable, especially when
unsetisn't used before interacting with it
- Using a variable, especially when
- The Java® Language
- Using a new package declaration
1.9. Avoid repeating yourself
Code that is duplicated to multiple places may accidentally be superficially similar but really a little bit different. This is extremely treacherous because the seeming familiarity raises false expectations. It's often better to make things distinctly different than to make them almost the same. Because of this, if at all possible, similar sequences of characters that appear in different parts of a program's source code should be used to make one function that can be re-used without adding many characters to a file, and that should be used to replace as many commands that do similar things as possible.
1.10. Use as few characters as possible to make your program match its documentation
Try to make your source code as short and easy to understand as possible.
Adding more text to source code is likely to be harmful, as it becomes harder to understand. Also, source code should define mechanism and not policy, so every change to source code has a chance to introduce an unwanted policy change, and creating a large amount of source code requires more changes than making a small amount of source code.
1.11. Make each git commit have as high code coverage using local testing as is reasonable
git-bisect is less useful if there is a lower code coverage or tests are added in a different commit from other changes.
Having higher code coverage also helps identify source code that is impossible to test in isolation, which should probably lead to refactoring or removing the questionable source code.
1.11.1. See also
1.12. Use a larger number of lines if it's possible to format them consistently
Usually, when using the Shell Command Language, the only way to be sure of where for commands or if commands or other Compound Commands start and end is by looking for words like done and fi with matching indentation. We also want to be able to quickly find which list(s) a command we're interested in is a part of, and what follows or precedes a given list. Because of this, inconsistent formatting will lead to source code that doesn't do what you expect it to more often than you'd like, as you are less likely to understand an inconsistently formatted program and will therefore be more likely to make harmful changes to it.
As your fixer program allows, put terminator and beginning words of compound commands as close to the start of a line they appear on as possible, and try to make there be as few characters that aren't an unquoted <blank> on each line as possible, and pay particular attention to lines with a Reserved Word (note that at least one word must follow each ! and case and for and fixers often add more visible characters after them on the same line, and this also applies to all reasonable uses of in, and do will usually get moved to the same line as a matching for word).
Because source code is more difficult to understand and navigate without consistent indentation, consistently increase the amount of indentation for lists that are a part of a new compound command you added. Also consistently decrease the amount of indentation you use by the same amount for commands you add after or before a compound command or when removing a compound command but leaving one of its lists behind. Consistently indenting with constant but unusual increments is better than indenting with inconsistent increments.
An additional benefit of using more lines is that future changes will change a smaller proportion of what git blame writes.
These considerations apply to many languages, not only the Shell Command Language.
For example, make your source code more like the first of these examples than the second:
for i in 1 2 3; do
{
ls -- "${i}"
if
[ "${i}" -gt 1 ]
then
echo "${i} is big"
else
echo "${i} is small"
fi
} ||
echo "something went wrong with ${i}"
done &&
seconds="$(date -u +%s)" &&
future_seconds="$((seconds + 10))" &&
current_seconds="$(date -u +%s)" &&
until
[ "${current_seconds}" -gt "${future_seconds}" ]
do
{
sleep 1 &&
current_seconds="$(date -u +%s)" &&
printf '%s\n' "${current_seconds}"
} ||
exit
done
for i in 1 2 3; do {
ls -- "${i}"
if [ "${i}" -gt 1 ]; then echo "${i} is big"; else echo "${i} is small"; fi
} || echo "something went wrong with ${i}"; done && seconds="$(date -u +%s)" && future_seconds="$((seconds + 10))" && current_seconds="$(date -u +%s)" && until [ "${current_seconds}" -gt "${future_seconds}" ]; do { sleep 1 && current_seconds="$(date -u +%s)" && printf '%s\n' "${current_seconds}" } || exit; done
Make your source code more like the first of these examples than the second:
for file in /bin /var; do
if
[ -e "${file}" ]
then
echo "${file}"
if
[ -n "${file#/*}" ]
then
echo "file begins with a /"
else
echo "file does not begin with a /"
fi
for entry in "${file}"/*; do
if
[ -f "${entry}" ]
then
ls -l -- "${entry}"
else
echo "${entry} was not a regular file"
fi
done
else
echo "${file} didn't exist"
fi
done
for file in /bin /var; do
if
[ -e "${file}" ]
then
echo "${file}"
if
[ -n "${file#/*}" ]
then
echo "file begins with a /"
else
echo "file does not begin with a /"
fi
for entry in "${file}"/*; do
if
[ -f "${entry}" ]
then
ls -l -- "${entry}"
else
echo "${entry} was not a regular file"
fi
done
else
echo "${file} didn't exist"
fi
done
1.13. Use null and "y" to represent boolean values with strings
Having the standard value representing "false" be null (as would be assigned with a command like myvar= && echo "${myvar}" or myvar="" && echo "${myvar}") makes commands like [ -z "${variable}" ] and echo "these variables are set with a non-null value:"${variable_1:+" variable_1"}${variable_2:+" variable_2"} possible to be useful, and they are useful sometimes. Also, using "set to the null string" as opposed to just leaving variables unset means that executing set -u is less likely to change later behavior, and testing for "unset or null" means that unintentionally leaving a variable unset will usually produce the same effects as it being set to "null".
Any value that causes expansions to result in strings with nonzero length are acceptable to represent "true" (so extra information can be stored apart from just a boolean setting), but the specific value of "y" may be used most widely, based on how POSIXLY_CORRECT gets set: this script writes only y and a newline character: unset -v -- POSIXLY_CORRECT && bash --posix -c -- 'printf "%s\n" "${POSIXLY_CORRECT}"'
1.14. Make comments easy to maintain
1.14.1. Avoid inline comments
Try to put comments about commands on a line with no commands, and directly preceding a command they describe, rather than after or within a command.
Putting comments and commands on the same line makes it possible that changing a command will add more text before a comment (thus making it less clear what the comment refers to) or change the padding of comments, and lines that are entirely comments are handled quite gracefully. Also, using comments on the same line as a command with an interactive shell can cause the command to get stored improperly in the history file, so makes manually testing programs more difficult.
For example, make source code more like the first of these snippets than the second:
# Writes text
echo hello
# Writes more text
for item in "${@}"; do
echo "a parameter is ${item}"
done
echo hello # Writes text
for item in "${@}"; do # Writes more text
echo "a parameter is ${item}"
done
1.14.2. Function comments
Putting comments about a function inside the function makes comparing edits about those comments easier, as git diff will put the name of the function above the edited comment rather than the name of an unrelated function.
1.15. Prefer to quote even when it's not necessary
Sometimes Word Expansions will have the same results regardless of whether quote (") characters are added in certain places, but double-quoting makes your intention clearer. For example, ${variable:+"variable was set"} and variable="$(command)" may be easier to understand than ${variable:+variable was set} and variable=$(command) even though they will probably cause the same results most of the time.
Having more quotes is sometimes required, depending on the interpreter being used.
1.16. Follow other style guides and advice
Coordinate with other programmers by following as many instructions in guides and discussions of technical topics as are compatible with each other. For example, see https://google.github.io/styleguide/shellguide.html and https://www.cis.upenn.edu/~cis120/archive/19fa/java_style.shtml and http://mywiki.wooledge.org/BashFAQ/048 / https://mywiki.wooledge.org/BashFAQ/006#eval and https://programming-motherfucker.com/
1.17. Reduce the amount of Shell Command Language source code by replacing it with other programs
There is much complexity involved in using the Shell Command Language, so if any degree of complex behavior is needed it is often better to depend upon other languages for the majority of processing.
Using the Shell Command Language, it is not easy to cause an exception and resume normal flow afterwards, so all issues have to be dealt with immediately with predefined steps or information about what the issue was will often be lost. This means that useful information about issues may be made unavailable to a user, intentionally or unintentionally.
Most of the state that affects a Shell Command Language program is global, so commands can easily unintentionally affect how future commands act. If this happens it will likely be to the detriment of a user.
By using languages other than the Shell Command Language we will likely avoid surprising behavior, so limititing programs written with the Shell Command Language to only running a single command like ./my_script.py or java will likely lead to more reliable and observable behavior. If it is absolutely required that a Shell Command Language program runs multiple commands, the program should be made as simple and easy to understand as possible.
1.18. Prefer to avoid using Python
Python has historically lacked a way to consistently use it for similar purposes in different environments, so maintaining systems that use it is likely not energy efficient.
2. General resources
Electronic Frontier Foundation
Comparison of instant messaging protocols
Distributed Social Network software and example server
List of self-hostable social media software and public instances
How to Find Your Lost Windows or Office Product Keys
https://itsfoss.com/github-alternatives/
"The most important thing in programming is naming" - Terry A. Davis
3. Extensions
- https://addons.mozilla.org/en-US/firefox/addon/adnauseam/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link
- https://addons.mozilla.org/en-US/firefox/addon/consent-o-matic/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link
- https://addons.mozilla.org/en-US/firefox/addon/youtube-shorts-redirect/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link
- https://addons.mozilla.org/en-US/firefox/addon/sponsorblock/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link
- https://addons.mozilla.org/en-US/firefox/addon/dearrow/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link
- https://addons.mozilla.org/en-US/firefox/addon/buster-captcha-solver/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link
- https://addons.mozilla.org/en-US/firefox/addon/multi-account-containers/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link
- https://addons.mozilla.org/en-US/firefox/addon/taler-wallet/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link
- https://addons.mozilla.org/en-US/firefox/addon/arconnect/reviews/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=addons-manager-reviews-link