General Advice for shell scripts

What do you advice for shell usage?

  • Do you use bash? If not, which one do you use? zsh, fish? Why do you do it?
  • Do you write #!/bin/bash or #!/bin/sh? Do you write fish exclusive scripts?
  • Do you have two folders, one for proven commands and one for experimental?
  • Do you publish/ share those commands?
  • Do you sync the folder between your server and your workstation?
  • What should’ve people told you what to do/ use?
  • good practice?
  • general advice?
  • is it bad practice to create a handful of commands like podup and poddown that replace podman compose up -d and podman compose down or podlog as podman logs -f --tail 20 $1 or podenter for podman exec -it “$1” /bin/sh?

Background

I started bookmarking every somewhat useful website. Whenever I search for something for a second time, it’ll popup as the first search result. I often search for the same linux commands as well. When I moved to atomic Fedora, I had to search for rpm-ostree (POV: it was a horrible command for me, as a new user, to remember) or sudo ostree admin pin 0. Usually, I bookmark the website and can get back to it. One day, I started putting everything into a .bashrc file. Sooner rather than later I discovered that I could simply add ~/bin to my $PATH variable and put many useful scripts or commands into it.

For the most part I simply used bash. I knew that you could somehow extend it but I never did. Recently, I switched to fish because it has tab completion. It is awesome and I should’ve had completion years ago. This is a game changer for me.

I hated that bash would write the whole path and I was annoyed by it. I added PS1="$ " to my ~/.bashrc file. When I need to know the path, I simply type pwd. Recently, I found starship which has themes and adds another line just for the path. It colorizes the output and highlights whenever I’m in a toolbox/distrobox. It is awesome.

atzanteol,

Shell scripts are one of the things that makes Linux what it is. They’re relatively easy to create, powerful, etc. It was the thing that drove me to it from Windows in the first place.

One thing I would recommend against is creating dozens of utility scripts and/or aliases for things you run frequently. I have found it’s much better in the long-run to simply learn the “proper” commands and switches. If you use them often enough you start to type them very quickly. When you create helpers you start to learn your own ecosystem and will be lost on any system that doesn’t have your suite of helper apps installed.

There are exceptions to this to be sure (e.g. I always alias ‘l=ls -FhlA’) but I would specifically avoid the podup and poddown ones myself. I’ve gotten very quick at typing “docker run -it --rm foo” just by rote repetition.

You’re free to do as you like though. Maybe you’ll only run Linux on your own desktop so that’s all that matters. But something to keep in mind. I would at least learn the commands very well first and then later alias or script them for convenience.

draughtcyclist,

I agree. However… I do have a public repo with my helper scripts in case I need to set them up on a new machine. best of both worlds!

MigratingtoLemmy,

Welcome to the world of funny when you come across an airgapped server which doesn’t have the tools you use.

Eg: RHEL doesn’t have vim installed, now I can deal with nano but I’m way slower to do that. Luckily IaC has made my life somewhat easier

draughtcyclist,

Agreed, IaC has helped that process a lot. I just used to curse.

ace_garp,
@ace_garp@lemmy.world avatar

Yes, using bash on all boxen.

Scripts start with #!/bin/sh ,because, that gives quicker execution times.

Any simple aliases, I put in .bash_aliases

Tried tcsh and zsh around 30yrs ago, all bash since then.

Hammerheart,

Do you have to chmod all your scripts when you include the shebang? Or do you have it configured to save with the right permissions?

ace_garp,
@ace_garp@lemmy.world avatar

I chmod 755 each manually. I’ve never tried the automatic way, sounds easier.

wuphysics87,

Several things

  • write bash and nothing else (except posix sh)
  • find a good way to take notes. It shouldn’t be in your bashrc
  • only write fish for fish config
  • use $!/usr/bin/env bash
GravitySpoiled,

Good idea I added a “iwish” command a while ago. Whenever I am pissed about gnome not being able to do something, or anything else that didn’t work as it should, I wrote “iwish gnome had only one extension app” and it would add a new line to my wishlist.md Maybe it would be good for notes too. inote bla

Hammerheart,

I love thay idea im gonna implement it tonight

nawordar,
  • Fish. Much, much saner defaults.
  • I am writing #!/usr/bin/env sh for dead simple scripts, so they will be a tiny bit more portable and run a tiny bit faster. The lack of arrays causes too much pain in longer scripts. I would love to use Fish, but it lacks a strict mode.
  • No, why would I?
  • I used to share all my dotfiles, scripts included, but I was too afraid that I would publish some secrets someday, so I stopped doing that. For synchronizing commands, aliases and other stuff between computers I use Chezmoi.
  • To use Fish instead of fighting with start up time of Zsh with hundreds of plugins
  • Always use the so-called “strict mode” in Bash, that is, the set -euo pipefail line. It will make Bash error on non-zero exit code, undefined variables and non-zero exit codes in commands in pipe. Also, always use shellcheck. It’s extremely easy to make a mistake in Bash. If you want to check the single command exit code manually, just wrap it in set +e and set -e.
  • Consider writing your scripts in Python. Like Bash, it also has some warts, but is multiplatform and easy to read. I have a snippet which contains some boilerplate like a main function definition with ArgumentParser instantiated. Then at the end of the script the main function is called wrapped in try … except KeyboardInterrupt: exit(130) which should be a default behavior.
  • Absolutely not a bad practice. If you need to use them on a remote server and can’t remember what they stand for, you can always execute type some_command. Oh, and read about abbreviations in Fish. It always expands the abbreviation, so you see what you execute.
DasFaultier, (edited )
  • I use bash, because I never had the time to learn anything else.
  • Like @jlsalvador said, I use the #!/usr/bin/env bash shebang.
  • Nope
  • Also nope
  • Nope. Shell scripts reside in Git repos on Gitlab/Gitea/Forgejo and are checked out using Ansible playbooks onto the servers as necessary.
  • For scripts? Python. Read this blog post by the great @isotopp. For interactive use? bash is just fine for me, though I’ve customized it using Starship and created some aliases to have colored/pretty output where possible.
  • Use shellcheck before running your scripts in production, err on the side of caution, set -o pipefail. There are best practices guides for Bash, use those and you’ll probably be fine.
  • Be prepared to shave yaks. Take breaks, touch grass, pet a dog. Use set -x inside your Bash script or bash -x scriptname on the CLI for debugging. Remember that you can always fallback to interactive CLI to test/prepare commands before you put them into your script. Think before you type. Test. Optimize only what needs optimization. Use long options for readability. And remember: Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows your address.
  • Nope, it’s absolutely not bad practice to create aliases to save you some typing in interactive shell. You shouldn’t use them inside your scripts though, because they might/will not be available in other environments.

I switched to fish because it has tab completion Yeah, so does Bash, just install it.

Oh, I also “curate” a list of Linux tools that I like, that are more modern alternatives to “traditional” Linux tools or that provide information I would otherwise not easily get. I’ll post i

ToolsDebian-Packages available- mtr - iputils-tracepath - iproute2 - zsh - httpie - aria2 - icdiff - progress - diffoscope - atop - powertop - ntopng - ethtool - nethogs - vnstat - ss - glances - discus - dstat - logwatch - swatch - multitail - lynis - ncdu (du-clone), alias du=“ncdu --color dark -rr -x --exclude .git --exclude node_modules” - nnn (fully-featured terminal file manager. It’s tiny, nearly 0-config and incredibly fast. github.com/jarun/nnn) - slurm - calcurse - newsbeuter - tig (“ncurses TUI for git. It’s great for reviewing and staging changes, viewing history and diffs.”) - qalc -ttyrec - taskwarrior - ttytter - ranger - ipcalc - pandoc - moreutils - googler - weechat - pdftk - abcde - dtrx - tload - ttyload - cockpit - sar - ht (hte Hex Editor) - dhex - ack (grep-clone) - silversearcher-ag (grep-clone) - ripgrep (“recursively searches file trees for content in files matching a regular expression. It’s extremely fast, and respects ignore files and binary files by default.”, github.com/BurntSushi/ripgrep) - exa (statt ls) the.exa.website (“replacement for ls with sensible defaults and added features like a tree view, git integration, and optional icons.”) - fzf (CLI fuzzy finder), alias preview=“fzf --preview ‘bat --color "always" {}’” - fd (simple, fast and user-friendly alternative to ‘find’, github.com/sharkdp/fd) -entr (watch-clone) - csvkit (awk-clone) - ccze (log coloring) - surfraw -hexyl (“hex viewer that uses Unicode characters and colour”, github.com/sharkdp/hexyl) -jq (“awk for JSON. It lets you transform and extract information from JSON documents”, stedolan.github.io/jq/) -pass (“password manager that uses GPG to store the passwords”, github.com/lunaryorn/mdcat) - restic (“backup tool that performs client side encryption, de-duplication and supports a variety of local and remote storage backends.”, restic.net) - mdp (Markdown Presentation on CLI) -grepcidr - qrencode - caca-utils (show images on the CLI) - fbi ( & fbgs) (show images in Framebuffer device) - fbcat (take screnshot on framebuffer device) - nmap - micro (CLI Text Editor, ab Debian 11, micro-editor.github.io) - masscan (github.com/robertdavidgraham/masscan) - socat (Nachfolger von netcat, www.heise.de/select/ix/2017/11/1509815804306324) - dc3dd (patched version of GNU dd with added features for computer forensics) - smem (memory reporting tool) - free (Show Linux server memory usage) - mpstat (Monitor multiprocessor usage on Linux, part of sysstat package) - pmap (Montor process memory usage on Linux, part of the procps) - monit (Process supervision) - oping & noping - saidar (Curses-basiertes Programm für die Anzeige von Live-Systemstatistiken) - reptyr (Tool for moving running programs between ptys) - gron (github.com/tomnomnom/gron, makes JSON greppable, kann HTTP-Requests absetzen) - jc (github.com/kellyjonbrazil/jc, CLI tool and python library that converts the output of popular command-line tools and file-types to JSON or Dictionaries. This allows piping of output to tools like jq and simplifying automation scripts.) - bat (cat-clone), alias cat=‘bat’ (“alternative to the common (mis)use of cat to print a file to the terminal. It supports syntax highlighting and - git integration.”, github.com/sharkdp/bat) - ioping (github.com/koct9i/ioping, simple disk I/0 latency measuring tool, auch für disk seek rate/iops/avg) - vd (Visidata, multipurpose terminal utility for exploring, cleaning, restructuring and analysing tabular data. Current supported sources are TSV, CSV, fixed-width text, JSON, SQLite, HTTP, HTML, .xls, and .xlsx) - pdfgrep - duf github.com/muesli/duf (combined df and du, ncurses-based) - nala (apt-alternate, gitlab.com/volian/nala, christitus.com/stop-using-apt/) - iprange - tldr - rmlint - nvtop (github.com/Syllo/nvtop, GPUs process monitoring for AMD, Intel and NVIDIA) - lf (lf (as in “list files”) is a terminal file manager written in Go with a heavy inspiration from ranger file manager) no Deb pkg avail- oh-my-zsh (ohmyz.sh) - webmin - observium - cheat (github.com/cheat/cheat, create and view interactive cheatsheets on the command-line.) - bropages - ipbt / its-playback-time - todo - earthquake - suplemon - Newsroom - unity - ired - wpe - prettyping (ping), alias ping=‘prettyping --nolegend’ - diff-so-fancy (diff-clone) - q (query CSV Files with SQL) harelba.github.io/q/- gping (ping with a graph in CLI) - http-prompt (install via pip) - alt (“finding the alternate to a file. E.g. the header for an implementation or the test for an implementation. I use it paired with Neovim”, github.com/uptech/alt) - chars (“shows information about Unicode characters matching a search term.”, github.com/antifuchs/chars) - dot (“dotfiles manager. It maintains a set of symlinks according to a mappings file”, github.com/ubnt-intrepid/dot) - dust (“alternative du -sh. It calculates the size of a directory tree, printing a summary of the largest items.”, github.com/bootandy/dust) - eva (“command line calculator similar to bc, with syntax highlighting and persistent history.”, github.com/NerdyPepper/eva) - hyperfine (“command line benchmarking tool. It allows you to benchmark commands with warmup and statistical analysis.”, github.com/sharkdp/hyperfine) - mdcat (“renders Markdown files in the terminal”, github.com/lunaryorn/mdcat) - podman (“alternative to Docker that does not require a daemon. Containers are run as the user running Podman so files written into the - host don’t end up owned by root. The CLI is largely compatible with the docker CLI.”, podman.io) - skim (“fuzzy finder. It can be used to fuzzy match input fed to it. I use it with Neovim and zsh for fuzzy matching file names.”) - z (“tracks your most used directories and allows you to jump to them with a partial name.”, github.com/rupa/z) - alias wetter_graph=‘finger dresden@graph.no’ - alias wetter_color=‘curl wttr.in’ - alias maps_cli=‘telnet mapscii.me’ - github.com/say4n/crappybird- asciicker.com- cbonsai gitlab.com/jallbrit/cbonsai- GNU poke binary editor www.jemarch.net/poke / git.savannah.gnu.org/cgit/poke.git- gdu GoDiskUsage github.com/dundee/gdu- Cirrus CLI github.com/cirruslabs/cirrus-- tuxi github.com/Bugswriter/tuxi personal CLI assistant - ngrep github.com/jpr5/ngrep- topgrade github.com/r-darwish/topgrade- ndiff nmap.org/ndiff/ compare nmap scans - natlas github.com/natlas/natlas- sift sift-tool.org grep-alternative - xplr github.com/sayanarijit/xplr (hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf) - croc github.com/schollz/croc (allows any two computers to simply and securely transfer files and folders, great for forensics) - slidev sli.dev (HTML5 presentations) - lfs github.com/Canop/lfs (df alternative) - vtop (github.com/MrRio/vtop) - gtop (github.com/aksakalli/gtop) - up (Ultimate Plumber github.com/akavel/up) - ttyd (github.com/tsl0922/ttyd, Share your terminal over the web) - nms (no more secrets, github.com/bartobri/no-more-secrets, A command line tool that recreates the famous data decryption effect - seen in the 1992 movie Sneakers.) - xsv (github.com/BurntSushi/xsv, A fast CSV command line toolkit written in Rust.) - fx (github.com/antonmedv/fx, Terminal JSON viewer) - ccat (github.com/owenthereal/ccat, colorized cat mit Syntax Highlighting) - delta (github.com/dandavison/delta, A syntax-highlighting pager for git, diff, and grep output. VORSICHT: Paket einer anderen Software mit gleichem Namen unter Debian Bullseye als Paket verfügbar!) - dyff (github.com/homeport/dyff, /ˈdʏf/ - diff tool for YAML files, and sometimes JSON)

___

DasFaultier,

Rest of the list:

Tools pt. 2- skim (github.com/lotabout/skim, Fuzzy finder in Rust) - choose (github.com/theryangeary/choose, A human-friendly and fast alternative to cut and (sometimes) awk) - sd (github.com/chmln/sd, wie sed, Intuitive find & replace CLI, mit regex) - map (github.com/soveran/map, Map lines from stdin to commands, gemütliche Variante von xargs mit einfacherer Syntax und weniger Funktionsumfang) - crush (github.com/liljencrantz/crush, Crush is a command line shell that is also a powerful modern programming language. Kann u.a. SQL-Statements) - xxh (github.com/xxh/xxh, Bring your favorite shell wherever you go through the ssh.) - starship (starship.rs, Shell-Prompt anpassen mit Nerdfont) - q (github.com/natesales/q, A tiny & colorful command line DNS client with support for UDP, TCP, DoT, DoH, DoQ and ODoH.) - gping (github.com/orf/gping, Ping, but with a graph) - broot (github.com/Canop/broot, A new way to see and navigate directory trees : dystroy.org/broot) - dust (github.com/bootandy/dust, intuitive du colored) - dutree (github.com/nachoparker/dutree, a tool to analyze file system usage written in Rust) - lsd (github.com/Peltoche/lsd, next-gen ls) - mcfly (github.com/cantino/mcfly, Fly through your shell history using neural nets) - procs (github.com/dalance/procs, A modern replacement for ps written in Rust, color, human readable, multi-column keword search) - bottom (github.com/ClementTsang/bottom, top replacement, cross-platform graphical process/system monitor, zoom support) - btop++ (github.com/aristocratos/btop, resource monitor CPU, RAM, IO, processes, IN SCHICK!!!, C+±continuation of bpytop github.com/aristocratos/bpytop) - musikcube (github.com/clangen/musikcube, cross-platform, terminal-based music player, audio engine, metadata indexer, and server in c++ with an ncurses TI, incl.Android App) - viu (github.com/atanunq/viu, Terminal image viewer with native support for iTerm and Kitty, auch animated gif) - glow (github.com/charmbracelet/glow, Render markdown on the CLI) - falsisign (gitlab.com/edouardklein/falsisign, For bureaucratic reasons, a colleague of mine had to print, sign, scan and send by email a high number of pages. To save trees, ink, time, and to stick it to the bureaucrats, I wrote this script.) - ponysay (github.com/erkin/ponysay, wie cowsay mit bunten Ponies) - sniffnet (github.com/GyulyVGC/sniffnet, cross-platform application to monitor your network traffic with ease, Debian-Pakete von GitHub verfügbar) - netop (github.com/ZingerLittleBee/netop, monitor network traffic with bpf) - corefreq (github.com/cyring/CoreFreq, CPU monitoring software for 64-bits Processors.) - ctop (github.com/bcicen/ctop, Top-like interface for container metrics) - dua (github.com/Byron/dua-cli, View disk space usage and delete unwanted data, fast.) - dust (github.com/bootandy/dust, A more intuitive version of du in rust) - helix editor - lnav (github.com/tstack/lnav Log navigator) - bottom (github.com/ClementTsang/bottom, another cross-platform graphical process/system monitor) - broot (github.com/Canop/broot, a different than ranger/lf approach to navigating folders) - mdr (github.com/michaelmure/mdr, a markdown viewer) - eza (github.com/eza-community/eza, modern ls, with cool features like file icons) - ouch (github.com/ouch-org/ouch, It’s a CLI tool for compressing and decompressing for various formats. such as .tar .zip 7z .gz .xz .lzma .bz .bz2 .lz4 .sz .zst .rar) - spotify-tui (github.com/Rigellute/spotify-tui, Spotify CLI frontend (Spotify via terminal)) - toilet (caca.zoy.org/wiki/toilet, turn text into ASCII art) DNS tools: - viewdns.info - dnslytics.com - dnsspy.io - leafdns.com - dnsdumpster.com - intodns.com - www.zonecut.net/dns - xip.io - nip.io - ptrarchive.com - www.whatsmydns.net - ceipam.eu/en/dnslookup.php - spyse.com/tools/dns-lookup - www.buddyns.com/delegation-lab Good stuff for pentesters and security researchers: - contained.af - cryptohack.org - 0x00sec.org - hack.me - chall.stypr.com - crackmes.one - hackxor.net - tryhackme.com - ctftime.org - ctflearn.com - picoctf.org bash ### .bashrc ### CUSTOM FUNCTIONS # https://www.linuxjournal.com/content/boost-productivity-bash-tips-and-tricks ftext () { grep -iIHrn --color=always “$1” . | less -R -r } duplicatefind (){ find -not -empty -type f -printf “%sn” | sort -rn | uniq -d | xargs -I{} -n1 find -type f -size {}c -print0 | xargs -0 md5sum | sort | uniq -w32 --all-repeated=separate } generateqr (){ # printf “$@” | curl -F-=<- qrenco.de printf “$@” | qrencode -t UTF8 -o - }

GravitySpoiled,

My brian has too little ram to process the list of packages 😂 good to know the rest!

DasFaultier,

Neither does mine, but, I keep it to test a new tool from time to time.

bionicjoey,

Do you use bash?

Personally I use Bash for scripting. It strikes the balance of being available on almost any system, while also being a bit more featureful than POSIX. For interactive use I bounce between bash and zsh depending on which machine I’m on.

Do you write #!/bin/bash or #!/bin/sh?

I start my shell scripts with #! /usr/bin/env bash. This is the best way of ensuring that the same bash interpreter is called that the user expects (even if more than one is present or if it is in an unusual location)

Do you have two folders, one for proven commands and one for experimental?

By commands, do you mean bash scripts? If so, I put the ones I have made relatively bulletproof in ~/bin/, as bash usually makes them automatically on the path with this particular folder name. If I’m working on a script and I don’t think it’s ready for that, or if it goes with a specific project/workflow, I will move it there.

Do you sync the folder between your server and your workstation?

No. I work on lots of servers, so for me it’s far more important to know the vanilla commands and tools rather than expect my home-made stuff to follow me everywhere.

good practice? general advice?

Pick a bash style guide and follow it. If a line is longer than 80 characters, find a better way of writing that logic. If your script file is longer than 200 lines, switch to a proper programming language like Python. Unless a variable is meant to interact with something outside of your script, don’t name it an all caps name.

is it bad practice to create a handful of commands like podup and poddown that replace podman compose up -d and podman compose down or podlog as podman logs -f --tail 20 $1 or podenter for podman exec -it “$1” /bin/sh?

This is a job for bash aliases.

aphlamingphoenix,

Good advice. I’ll add that any time you have to parse command line arguments with any real complexity you should probably be using Python or something. I’ve seen bash scripts where 200+ lines are dedicated to just reading parameters. It’s too much effort and too error prone.

bionicjoey,

It depends. Parsing commands can be done in a very lightweight way if you follow the bash philosophy of positional/readline programming rather than object oriented programming. Basically, think of each line of input (including the command line) as a list data structure of space-separated values, since that’s the underlying philosophy of all POSIX shells.

Bash is basically a text-oriented language rather than an object-oriented language. All data structures are actually strings. This is aligned with the UNIX philosophy of using textual byte streams as the standard interface between programs. You can do a surprising amount in pure bash once you appreciate and internalize this.

My preferred approach for CLI flag parsing is to use a case-esac switch block inside a while loop where each flag is a case, and then within the block for each case, you use the shift builtin to consume the args like a queue. Again, it works well enough if you want a little bit of CLI in your script, but if it grows too large you should probably migrate to a general purpose language.

bionicjoey, (edited )

Here’s a simple example of what I mean:


<span style="color:#323232;">#! /usr/bin/env bash
</span><span style="color:#323232;">
</span><span style="color:#323232;">while [[ -n $1 ]]; do
</span><span style="color:#323232;">  case $1 in
</span><span style="color:#323232;">    -a) echo "flag A is set" ;;
</span><span style="color:#323232;">    -b|--bee) echo "flag B is set" ;;
</span><span style="color:#323232;">    -c) shift; echo "flag C is $1" ;;
</span><span style="color:#323232;">    --dee=*) echo "flag D is ${1#--dee=}" ;;
</span><span style="color:#323232;">  esac
</span><span style="color:#323232;">  shift
</span><span style="color:#323232;">done
</span>

Showing how to do long flags with B and flags with parameters with C and D. The parameters will correctly work with quoted strings with spaces, so for example you could call this script with –dee=“foo bar” and it will work as expected.

MigratingtoLemmy,

Hoho, now do that in POSIX shell.

I had a rude awakening the day I tried it, but my scripts are bulletproof now (I think) so I don’t mind at this point

bionicjoey,

Imma be real, I never remember which parts of bash aren’t POSIX. Luckily it doesn’t matter in my line of work, but it’s good to be aware of if you have a job that often has you in machines running other types of UNIX.

MigratingtoLemmy,

Arguments don’t work the same way and POSIX doesn’t have the concept of arrays outside of @

crispy_kilt,

Use shellcheck

llii,

shellcheck

That looks useful.

www.shellcheck.net

MigratingtoLemmy,

I use sh to attempt to keep it compatible with POSIX systems.

I use pain bash. Never really tried zsh and fish, since most of my Linux work is on servers and I don’t really care for extra features.

I try and write idempotent scripts when possible.

I wouldn’t create those aliases on a fleet because writing them to the configuration file of your shell in an idempotent fashion is hacky and my VMs are like cattle.

jbd,

I use fish shell only now. Used to only write bash, but I’ve started writing some fish scripts. I wouldn’t try to plan too much WRT shell scripting up front. Just fix your pain points as you go.

EpicVision,

Do you use bash? If not, which one do you use? zsh, fish? Why do you do it?

Mostly fish, because it just feels much more modern than bash, it has good built-in autocomplete and I don’t have to install millions of plugins like of zsh.

Do you write #!/bin/bash or #!/bin/sh? Do you write fish exclusive scripts?

#!/usr/bin/env bashOccasionally I also write fish scripts. Just replace sh with fish.

What should’ve people told you what to do/ use?

zoxide

general advice?

As @crispy_kilt already suggested, use shellcheck.

is it bad practice to create a handful of commands like podup and poddown that replace podman compose up -d and podman compose down or podlog as podman logs -f --tail 20 $1 or podenter for podman exec -it “$1” /bin/sh?

I don’t think so

tfowinder,

Use

set -x

For debugging

GravitySpoiled,

Good to know!

taladar,

I use bash for scripts almost exclusively even though i use zsh interactively (startup scripts for zsh are an obvious exception).

The vast majority of my scripts start with


<span style="color:#323232;">  set -e -u
</span>

which makes the script exit if a command (that is not in a few special places like an if) exits with an error status code and also complains about unbound variables when you use them.

Use


<span style="color:#323232;">bash -n
</span>

and


<span style="color:#323232;">shellcheck
</span>

to test your script for errors and problems if you try it.

Always use curly braces for variables to avoid issues with strings after the variable name being interpreted as part of the variable name.

Always use 10# before numbers in $(()) expressions to avoid leading zeroes turning your decimal number variables into octal ones.

Always use


<span style="color:#323232;">while read -r foo
</span><span style="color:#323232;">do
</span><span style="color:#323232;">...
</span><span style="color:#323232;">done < <(command ...)
</span>

instead of


<span style="color:#323232;">command ... | while read -r foo
</span><span style="color:#323232;">do
</span><span style="color:#323232;">...
</span><span style="color:#323232;">done
</span>

to avoid creating a subshell where some changes you make will not affect your script outside the loop.

In


<span style="color:#323232;">while read -r foo
</span><span style="color:#323232;">do
</span><span style="color:#323232;">...
</span><span style="color:#323232;">done < ...
</span>

loops always make sure you redirect all stdin from /dev/null or otherwise close it with suitable parameters or the content of your loop will eat some of the lines you meant for the read. Alternatively fill a bash array in the loop and then use a for loop to call your commands and do more complex logic.

When using temporary directories or similar resources use


<span style="color:#323232;">cleanup()
</span><span style="color:#323232;">{
</span><span style="color:#323232;"> ...
</span><span style="color:#323232;">}
</span><span style="color:#323232;">trap cleanup EXIT
</span>

handlers to clean up after the script in case it dies or is killed (by SIGTERM or SIGINT,…; obviously not SIGKILL).

When writing scripts for cronjobs take into account that the environment (PATH In particular) might be more limited. Also take into account that stderr output and non-zero exit status can lead to an email about the cronjob.

Use pushd and popd instead of cd (especially cd …), redirect their output to /dev/null. This will prevent your scripts from accidentally running later parts of the script in a wrong directory.

There are probably many other things to consider but that is just standard stuff off the top of my head.

If you do need any sort of data structure and in particular arrays of data structures use a proper programming language. I would recommend Rust since a compiled language is much easier to run on a variety of systems than the Python so many others here recommend, especially if you need to support the oldest supported version of an OS and the newest one at the same time.

atzanteol,

Great list! I would add “always surround variables with quotes in case the value contains spaces”.

taladar,

Good point, forgot one of the basics.

Also, to make your scripts more readable and less error prone use something like


<span style="color:#323232;">if [[ $# -gt 0 ]] && [[ "$1" == "--dry-run" ]]; then
</span><span style="color:#323232;">  dry_run=1
</span><span style="color:#323232;">  shift
</span><span style="color:#323232;">else
</span><span style="color:#323232;">  dry_run=0
</span><span style="color:#323232;">fi
</span><span style="color:#323232;">
</span><span style="color:#323232;">if [[ $# != 3 ]]; then
</span><span style="color:#323232;">  echo "Usage: $0 [ --dry-run ] <description of foo> <description of bar> <description of baz>" >&2
</span><span style="color:#323232;">  exit 1
</span><span style="color:#323232;">fi
</span><span style="color:#323232;">
</span><span style="color:#323232;">foo="$1"
</span><span style="color:#323232;">shift
</span><span style="color:#323232;">bar="$1"
</span><span style="color:#323232;">shift
</span><span style="color:#323232;">baz="$1"
</span><span style="color:#323232;">shift
</span>

at the start of your script to name your parameters and provide usage information if the parameters did not match what you expected. The shift and use of $1 at the bottom allows for easy addition and removal of parameters anywhere without renumbering the variables.

Obviously this is only for the 90% of scripts that do not have overly complex parameter needs. For those you probably want to use something like getopt or another language with libraries like the excellent clap crate in Rust.

GravitySpoiled,

Thankyou very much!

Pantherina,

Yes fish is great. It has some special syntax for functions, I will add my configs soo.

set fish_greeting is useful to silence it.

User scripts can go to ~/.local/bin which is already in the path.

You can split up your shell configs into topics, and put them into ~/.config/fish/conf.d/abc.conf

redxef,
  • I usually use bash/python/perl if I can be sure that it will be available on all systems I intend to run the scripts. A notable exception for this would be alpine based containers, there it’s nearly exclusively #!/bin/sh.
  • Depending on the complexity I will either have a git repository for all random scripts I need and not test them, or a single repo per script with Integrationtests.
  • Depends, if they are specific to my setup, no, otherwise the git repository is public on my git server.
  • Usually no, because the servers are not always under my direct control, so the scripts that are on servers are specific to that server/the server fleet.
  • Regarding your last question in the list: You do you, I personally don’t, partly because of my previous point. A lot of servers are “cattle” provisioned and destroyed on a whim. I would have to sync those modifications to all machines to effectively use them, which is not always possible. So I also don’t do this on any personal devices, because I don’t want to build muscle memory that doesn’t apply everywhere.
BrianTheeBiscuiteer,

Bash script for simple things (although Fish is my regular shell) and Node or Python scripts for complex things. Using #!/usr/bin/env node works just like it would for Bash so you know.

lolcatnip,

This. I still write plenty of bash scripts, but I’ve noticed that except for really simple cases, I very quickly reach a point where I would have been better off using Python instead. And when I start with a Python scripts I often end up redoing it in Rust for a variety of reasons. It’s just easy to underestimate how serious a programming project is. At least I’ve never started something in bash that I needed to migrate to Rust.

(I think a lot of people would see Go as the next logical step from Python, but I personally find some things about Go really irritating.)

  • All
  • Subscribed
  • Moderated
  • Favorites
  • linux@lemmy.ml
  • fightinggames
  • All magazines