top | item 37112991

Ask HN: Share a shell script you like

70 points| yu3zhou4 | 2 years ago | reply

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!

82 comments

order
[+] SushiHippie|2 years ago|reply
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|2 years ago|reply
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.

[+] UI_at_80x24|2 years ago|reply
> timestamp() {...etc...}

I prefer that info to be in my PS1. PS1="\[\e[0;36m\[\e[m\] \[\e[0;36m\][\w]\[\e[m\]\n\t \u@\h \[\e[0;36m\]\$ \[\033[0m\]"

Double-tapping Enter before I run a command where I'll want to know the time I launched it as, solves the "when did I launch this?" question.

Example output: [~] 10:22:16 username@hostname $

[+] swores|2 years ago|reply
You may not see this two weeks later comment, but in case you do have reply notifications or check the threads page:

Thanks for the timestamp function! I can't believe it never occurred to me to do something like that before, and unlike you I think I actually will be using it now and then :)

[+] ray_v|2 years ago|reply
I like using this little trick to change the directory to the one in which the script resides...

  #!/bin/bash
  cd "$(dirname "$0")"
[+] KolenCh|2 years ago|reply
This would have a problem if the script is symlinked.
[+] saurik|2 years ago|reply

    #!/bin/bash
    cd "${0%/*}/"
[+] GhostWhisperer|2 years ago|reply
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
[+] nektro|2 years ago|reply

    ccd() {
        mkdir -p $1
        cd $1
    }
    cdtemp() {
        cd $(mktemp -d)
    }
[+] Leftium|2 years ago|reply
mine's called cdd, and it's a little more complex:

    cdd () {
      case "$1" in
        */..|*/../) cd -- "$1";; # that doesn't make any sense unless the directory already exists
        /*/../*) (cd "${1%/../*}/.." && mkdir -p "./${1##*/../}") && cd -- "$1";;
        /*) mkdir -p "$1" && cd "$1";;
        */../*) (cd "./${1%/../*}/.." && mkdir -p "./${1##*/../}") && cd "./$1";;
        ../*) (cd .. && mkdir -p "${1#.}") && cd "$1";;
        *) mkdir -p "./$1" && cd "./$1";;
      esac
    }
https://unix.stackexchange.com/a/9124/4791
[+] spelufo|2 years ago|reply
Same here. I call it mkcd
[+] GhostWhisperer|2 years ago|reply
oh yea, slight name difference:

    alias mkdir='mkdir -pv'
    function take() {
      mkdir $1
      cd $1
    }
i'm going to steal that cdtemp function though
[+] hnfong|2 years ago|reply
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|2 years ago|reply
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;
[+] N0YS|2 years ago|reply
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.

[+] dyingkneepad|2 years ago|reply
Nothing will ever beat this one: alias s='cd ..'

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

[+] tpoacher|2 years ago|reply
why 's', and not, say, 'up'?
[+] w10-1|2 years ago|reply
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|2 years ago|reply
- 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.

[+] saurik|2 years ago|reply
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).
[+] lathiat|2 years ago|reply
Have you tried “watch”? It buffers and doesn’t flicker for me.
[+] akkartik|2 years ago|reply
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|2 years ago|reply
I use this one all the time:

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