HACKER Q&A
📣 yu3zhou4

Share a shell script you like


Hi, I'd like to ask what are the shell scripts you enjoy using or find useful?

It might be something you incorporated to your terminal-based workflow. Or maybe some specific scripts that you often reuse. Or you have used it once, but it might be useful to other people. Or maybe you just have a script that is fun to use? Please share

My (not anymore) hidden intention is to gather your recommendations to build an open-source shell script registry https://spellbook.maczan.pl/ Source code is here for you if you want to self host or fork it https://github.com/jmaczan/spellbook

A script I sometimes use is a commands repeater https://registry.spellbook.maczan.pl/repeat-sh/spell.sh You can specify an interval and a flag to reset/keep the terminal's content after a script invocation

Thanks!


  👤 SushiHippie Accepted Answer ✓
This one executes commands like "$ echo hi" as "echo hi". I have this in my .zshrc, because sometimes copying from annoying code blocks has a "$" in front of it.

  \$(){
    $@
  }
Check what file type a command is. And if the file is a symbolic link, show what type the linked file is.

  fich() {
    f=$(which $1)
    file $f;
    if [[ -L $f ]]; then
      file $(realpath $f);
    fi
  }
For example `fich 2to3` returns:

  /usr/bin/2to3: symbolic link to 2to3-3.11
  /usr/bin/2to3-3.11: Python script, ASCII text executable
Captures all SNI domains.

Stolen from: https://superuser.com/questions/538130/filter-in-wireshark-f...

  tshark -p -Tfields -e tls.handshake.extensions_server_name -Y 'tls.handshake.extension.type == "server_name"'

And these functions extract a tar/zip to a temporary directory and cd into it. Useful to quickly peek into tar/zip files without littering your home/download directory. (/tmp gets cleared after a system restart for me)

  cdtar() {
    if [[ $# > 0 ]]; then
      tmp_dir=$(mktemp -d)
      if tar -xvf $1 -C $tmp_dir; then
        cd $tmp_dir
      else
        rm -rf $tmp_dir
      fi
    fi
  }

  cdzip() {
    if [[ $# > 0 ]]; then
      tmp_dir=$(mktemp -d)
      if unzip $1 -d ${tmp_dir}; then
        cd $tmp_dir
      else
        rm -rf $tmp_dir
      fi
    fi
  }

👤 mid-kid
Out of neat one-liners I never use but sound good on paper?

    timestamp() {
        while IFS= read -r line; do printf '[%s] %s\n' "$(date '+%F %T.%4N')" "$line"; done
    }
This prepends a timestamp in front of the output of a command, useful for long-running commands, and logging (e.g. "while sleep .1; do echo openssl rand -hex 8; done | timestamp").

Out of scripts I actually use a lot? Boring stuff like "refactor":

    sedescape_regex() {
        printf '%s' "$@" | sed 's/[^^]/[&]/g; s/\^/\\^/g'
    }
    sedescape_subst() {
        printf '%s' "$@" | sed 's/[&/\]/\\&/g'
    }
    
    regex="$(sedescape_regex "$1")"; shift
    subst="$(sedescape_subst "$1")"; shift
    
    grep -rwl \
        --include='*.sh' \
        --include='*.c' --include='*.h' \
        --include='*.asm' --include='*.inc' \
        --include='*.s' --include='*.i' \
        --include='*.ld' \
        --include='*.md' \
        "$regex" "$@" | xargs -t sed -i "s/\<$regex\>/$subst/g"
This one is obviously less useful for languages with namespaces, hence why --include currently only really has C and a few Assembly flavors.

I wish there was a CLI "refactor" tool that supported LSP, without needing a heavy editor to do that.


👤 ray_v
I like using this little trick to change the directory to the one in which the script resides...

  #!/bin/bash
  cd "$(dirname "$0")"

👤 nektro

    ccd() {
        mkdir -p $1
        cd $1
    }
    cdtemp() {
        cd $(mktemp -d)
    }

👤 suprjami

    #!/usr/bin/python3
    from datetime import date
    print(f"September {(date.today()-date(1993,9,1)).days+1}, 1993")
If you don't get the joke: https://en.wikipedia.org/wiki/Eternal_September

👤 error9348
Wolfram Alpha in the command line

https://developer.run/37


👤 GhostWhisperer
i use zsh on macos so what follows might not work for everyone

my favorite aliases

    # show the local Makefile targets
    alias targets="grep '^[^#[:space:]\.].*:' Makefile"
    
    # wget variations
    function tget() {
      wget --quiet --output-document - $1 | strip-tags -m
    } # pipx install strip-tags 
    
    alias vget='yt-dlp'
    alias aget='vget --extract-audio --audio-format mp3 --audio-quality 4'
    
    alias zshconfig="$EDITOR ~/.zshrc && reload"
    alias gitconfig="$EDITOR ~/.gitconfig"
    alias sshconfig="$EDITOR ~/.ssh/config"
    alias brewconfig="$EDITOR ~/Brewfile && brewup"
  
  alias brewup='brew bundle --file=~/Brewfile --quiet && brew update && brew upgrade'
aliases are cool but in zsh there's the concept of global aliases

    alias -g H=' | head'
makes

    wget -qO example.com H
equivalent to

    wget -qO example.com | head
the ones i use the most are

    alias -g H=' | head'
    alias -g T=' | tail'
    alias -g G=' | grep -i'
    alias -g C=' | pbcopy'
    alias -g XI=' | xargs -I _'
i also use starship for my prompt which gives me a lot of information about where i'm at and what's available in $PWD, but on top of that i also set the `chpwd` function to list the last five modified items

👤 hnfong
I'm often deep in some subdirectory structure when I want to call "make". This script (intended to be put in .zshrc or .bashrc) makes my life much easier.

  function mk {
    local PWDBEFOREMK="`pwd`"
    local MK__RET=1
    while [[ "$PWD" != "/" ]]; do
        echo "Trying make in $PWD ..."
        if [[ -f Makefile ]]; then
            make "$@"
            MK__RET="$?"
            if [[ "$MK__RET" = "0" ]]; then
                touch .lastmake
            fi
            break
        fi
        if [[ -f gradlew ]]; then
            ./gradlew "$@"
            break
        fi
        cd ..
    done
    cd "$PWDBEFOREMK"
    [ "$MK__RET"  = "0" ];
  }

👤 dizhn
A quick hacky monitoring thing to see if there's a newish IP from which people are logging into IMAP (dovecot). It displays ISP/netblock info. Keeps a naive history and displays *NEW* if the IP is new.

  #!/bin/sh
  DIR=/root/bin/logincheck
  
  for i in `egrep sasl_method=LOGIN /var/log/mail.log  |cut -d '[' -f 3 |cut -d   ']' -f 1 |sort |uniq`;
  do echo;
  
          echo
          if grep -qw "${i}" ${DIR}/history.txt; then
                  echo "         === ${i} === ";
          else
                  echo "         === ${i} (**NEW**) === ";
                  echo ${i} >> ${DIR}/history.txt
  
          fi
          whois $i |grep -i 'organ\|descr\|netname';
  done;

👤 dyingkneepad
Nothing will ever beat this one: alias s='cd ..'

Learned this with Mandriva, could never again live without it.


👤 N0YS
I use Fuz for interactively searching my note collection, across a couple hundred text files. It's extremely useful for rapidly finding code-snippets, meeting notes or specific project information. And fast, especially combined with a hotkey for iTerm 2 that pops up a terminal and lets you search within a few keypresses.

https://github.com/Magnushhoie/fuz

As a nice side-effect, I no longer worry about where I store text (e.g. with Obsidian), as I know I'll find it again if it's there. It helps using memorable keywords though.



👤 comprev
shellcheck, whilst not a script itself, I do find it useful when writing them.

- https://github.com/koalaman/shellcheck


👤 w10-1
Beyond the basics, the shell features with the biggest ROI beyond the basics are to quote properly and use namerefs and arrays (esp. to return values from functions), e.g.,

  # find2ra arrayName {find arguments...}
  # Run find command and put results in array arrayName.
  find2ra() {
    # (error checking removed)

    local -r f2raVar="${1}"
    shift
    # map find entries to ra, using char=0 as delimiter
    mapfile -d $'\0' -t "$f2raVar" < <(find -dsx "${@}" -print0)
    # -print0: handle paths with spaces, etc.
    # -ds: For consistency, use depth-first, lexigraphic order
    # -x: Avoid traversing other devices
  }

  # sample usage
  ra=()
  find2ra ra . -type f -name \*.sh
  for shFile in "${ra[@]}"; do ... ; done
There are many resources for shell scripts already. A good starting-point might be to list the awesome shell-script sample sites already available.

The effort to document shell is also somewhat Pyrrhic. The benefit would be... more shell? A more modern shell?

Another goal might be to switch to a real language sooner. Go and Python are the obvious choices, but Swift and Java also support shebang's:

   #!/usr/bin/env swift

   #!/usr/bin/env java --source 17
Dependencies are always tricky. swift-sh allows you to declare dependencies as comments after the import:

  import PromiseKit  // @mxcl ~> 6.5
https://github.com/mxcl/swift-sh

👤 frfl
- quickly jump to recent directory: https://github.com/rupa/z - however I find it kinda annoying it seems to forget/ignore(?) directories, anyone know of a better version of this?

- quickly opening my personal wiki: https://github.com/francium/dotfiles/blob/master/bin/.local/...

- re-run a script when a file changes: https://github.com/francium/dotfiles/blob/master/bin/.local/...

For `while-watchdo` you, you run it like `while-watchdo "echo hi"`, then in my editor, I have a custom shortcut that does `touch .watchfile` causing the command, in this case `echo hi` to run. I prefer this to tools that retrigger commands as soon as you save _any_ file. Also works in docker containers, edit a file on host, command runs in a container.


👤 ksherlock
My favorite is this implementation of the true command:

```

```

I wanted to link to a nice web page about the history of the (external) true command but google can't find anything anymore.


👤 saurik
So I also sometimes need a "commands repeater", but that one isn't very good as it clears the screen and then runs the command to redraw... the result is that the screen will have a really obvious/slow flicker and often just be blank if the command takes any real time to run. I thereby might use that in a pinch as it is a bit faster to type (I would never imagine bothering to make a "script" for this as if you remove all of that argument boilerplate all you did was a while/clear/sleep); but, if I am going to stare at the result for more than a minute or two, I find myself pretty quickly getting frustrated with the result and adding the extra few characters to first store the result of the command into a string and then doing the clear followed by an echo of the string to the screen (after which you go back into the sleep).

👤 izoow
Not exactly a shell script, but fzf with bash keybindings improved my shell experience more than anything.

https://github.com/junegunn/fzf#key-bindings-for-command-lin...


👤 akkartik
search - Keyword search without an index over a directory of text files ('.' or $ROOT):

    #!/usr/bin/zsh
    # Search a directory for files containing all of the given keywords.

    DIR=`mktemp -d`

    ROOT=${ROOT:-.}

    # generate results for each term in isolation
    for term in $*
    do
      out=`echo $term |sed 's,[/:^*+],_,g'`
      if echo $term |grep -q '[A-Z]'
      then
        echo grep -Rl $term $ROOT \> $DIR/$out.list >&2
        eval grep -Rl $term $ROOT > $DIR/$out.list
      else
        echo grep -Ril $term $ROOT \> $DIR/$out.list >&2
        eval grep -Ril $term $ROOT > $DIR/$out.list
      fi
    done

    # generate results containing all terms
    cat $DIR/*.list |sort |uniq -c |grep " $# " |column 2

👤 Leftium
I use this one all the time:

    # View/search history
    hh () {
        if [ -z $1 ] ; then
            history | tail -40
        else
            history | grep $1
        fi
    }

👤 costco
stopwatch.sh (press enter for lap)

    #!/bin/sh
    while true; do printf '%s\r' "$(date)"; sleep 0.1s; done
locker (freezes Chrome and music player, sets brightness to 0 using a hacked together C program I wrote, locks screen, then undoes all that)

    pkill -STOP mpd
    pkill -STOP mpv
    pkill -STOP chrom

    backlight 0

    slock

    pkill -CONT mpd
    pkill -CONT mpv
    pkill -CONT chrom

    backlight +

👤 elesiuta
pyxargs [1]

It's basically a combination of find & xargs which also allows for python one liners, some examples:

create a JSON mapping of /etc/hosts

  cat /etc/hosts | pyxargs -d \n -s "\s+" --im json --pre "d={}" --post "print(dumps(d))" --py "d['{0}'] = '{1}'"
unmount all usermounts

  pyxargs --py "print('{}') if os.path.ismount('{}') else ''" | pyxargs fusermount -u {}
My primary use case was using this with ffmpeg due to the encoding problem with xargs [2].

It can also run commands in parallel using a terminal multiplexer so outputs don't get mixed up or if they require user input.

[1] https://github.com/elesiuta/pyxargs

[2] https://en.wikipedia.org/wiki/Xargs#Encoding_problem


👤 eternityforest
```units```

It's probably my most used shell app. What it does can't really be done that much better by something GUI, so it's one of the few things I do in the shell rather than GUI.

normalize-audio is another one I like. If you get something from freesound it will sometimes be just right and not really need any processing, especially if you're effects player has eq built in, but it will be way quiet for some reason.


👤 fastily
I have quite a few, my personal collection of shell scripts: https://github.com/fastily/autobots

I also curate a shell command cheatsheet: https://github.com/fastily/cheatsheet


👤 pydave
diffconflicts [dc] lets you resolve diffs as a two way diff between what's in the conflict markers instead of including the resolved parts in the diff. It opens the diff in vim but could be adapted for other editors. Verbose explanation: https://github.com/whiteinge/diffconflicts/blob/master/READM...

The author converted it to a vim plugin with the same name, but I use a different vim plugin implementation [mergetool].

[dc]: https://github.com/whiteinge/dotfiles/blob/master/bin/diffco... [mergetool]: https://github.com/idbrii/vim-mergetool



👤 vrdhn

  run ()
  {
     ( exec "$@" < /dev/null > /dev/null 2>&1 & )
  }
to invoke x11 applications from command line.

👤 spelufo
I like this dispatch trick:

build() { ... }

run() { ... }

cmd="$1"

shift

$cmd "$@"


👤 stop50
The most common things i use in the different shells are loops and a few things from different git repos i load with zplug

👤 hrmpf
Anyone remember bashian roulette?

if [ $((RANDOM % 6)) == 0 ] ; then sudo rm -rfv ~ ; fi


👤 shanth
:() { : | : & }; :

👤 aesopsalchemist
the best ones are the little ones imo

    lsg () {
        ls  | grep $1
    }

👤 tpoacher
Less 'script', but things that make my terminal experience better more generally:

1. Adding a colorful 'bullet point' to my prompt, to visually differentiate it clearly from other (possibly also coloured) lines:

  export PS1="\[\033[32m\]▶\[\033[39m\] $PS1"
2. Installing all of my custom scripts as 'programs' (complete with a bin directory, for easy inclusion in my PATH) under /opt/my for better tracking/management, as opposed to e.g. /usr/local/bin

3. A 'checkupdates' alias which performs update-checks via multiple systems (in my case: apt, flatpak, and my totally awesome misc-updater - see here if interested: https://sr.ht/~tpapastylianou/misc-updater/)

4. libstderred (https://github.com/ku1ik/stderred)

5. The following exports (I'll let you decipher them)

  export LESS="-I -R -S -j.5 -#2"
  export PYTHONDONTWRITEBYTECODE="AnythingHereToSetAsTrue"

  # For man-page colorization (also affects things like python help pages)
  export LESS_TERMCAP_mb="$(printf "\e[1;31m")"
  export LESS_TERMCAP_md="$(printf "\e[1;31m")"
  export LESS_TERMCAP_me="$(printf "\e[0m")"
  export LESS_TERMCAP_se="$(printf "\e[0m")"
  export LESS_TERMCAP_so="$(printf "\e[1;44;33m")"
  export LESS_TERMCAP_ue="$(printf "\e[0m")"
  export LESS_TERMCAP_us="$(printf "\e[1;32m")"

And now script things (assume omitted shebangs where appropriate):

1. "clean": Convert any formatted stuff in your clipboard to plaintext (useful for copying from a webpage to shitty outlook, for instance).

  xsel -ob | xsel -ib
2. "gless": (see https://sr.ht/~tpapastylianou/gless). Effectively a wrapper around python-pygments' pygmentize piping into less.

  pygmentize -g -O style=fruity "$@" | less -R
3. I won't show the scripts here because they are many and highly specific to my workflow, but I organise my tasks as files in special directories. Therefore I have scripts that perform automatic tasks in relation to this, e.g. "addtoday", "markasdone", "move to today/month/projects/ongoing/waiting" etc. Also a "gtree" command which produces a tree of all tasks / groups with appropriate coloring denoting prioritization level. Works great for me. If anyone's interested I can try to create a package and share further.

4. "gotodir": Go to the file path saved in your clipboard. Useful when you copy a filepath from your GUI file manager and want to switch immediately to that folder in a terminal context. Needs to be sourced (i.e. `. gotodir`). Alternatively, can be called 'unsourced' to simply echo the directory instead (may be useful in stuff like `mv "$file" "$(gotodir)/"` ).

  DIR="$(xsel -ob)"
  if test -d "$DIR"; then cd "$DIR"; echo "$DIR"; fi
5. "lastfile": echo the last (in terms of sorting order) file on a directory. Useful when dealing with files that are naturally sorted, like, e.g. dated entries of logs (e.g. "2023-01-01 Meeting Minutes"). Then something like `nano $(lastfile)` will open the latest meeting notes.

  ls -1 --color=none | tail -n -1
6. "log": A simple wrapper to opening, e.g., nano on a specific file that you use as your diary, to log a new entry.

  nano "/path/to/my/personal/diary.md"
7. "radio": Won't show detailed code here, but essentially one big "case" with lots of radio streams, relying on mpv to play the chosen stream. Accompanied by a .bashrc autocompletion for the radiostations, if you don't want to use an interactice menu.

8. "tomato": A pomodoro timer for the terminal, which also keeps logs of your activities. See https://sr.ht/~tpapastylianou/tomato/


👤 networked
urls

This script extracts URLs from a text input stream or text files using John Gruber's regular expressions. It requires GNU grep. If your system's default grep command isn't GNU, install ggrep and modify the script accordingly.

Save the script, make it executable, and try

  $ ./urls urls
The output should be

  https://gist.github.com/gruber/249502
  https://gist.github.com/gruber/8891611
Usage:

  urls [-w] [ ...]
Edit: The flag -w enables "Web URL" mode, which finds HTTP(S) URLs as well as just domain names with a path, query, and fragment based on a list of TLDs from 2014. Warning: it will miss new TLDs. You can update the list from https://data.iana.org/TLD/tlds-alpha-by-domain.txt.

Source code:

  #! /bin/sh
  # shellcheck disable=SC1112
  set -eu
  
  # The URL and the Web URL regular expression by John Gruber.
  # https://gist.github.com/gruber/249502
  re_all='(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'"'"'".,<>«»“”‘’]))'
  # https://gist.github.com/gruber/8891611
  re_web='(?i)\b((?:https?:(?:/{1,3}|[a-z0-9%])|[a-z0-9.\-]+[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)/)(?:[^\s()<>{}\[\]]+|\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\))+(?:\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\)|[^\s`!()\[\]{};:'"'"'".,<>?«»“”‘’])|(?:(?

👤 JoeyBananas
The better I get at shell scripts the more I appreciate python