36 votes

Share your useful shell scripts!

Disclaimer: Don't run scripts offered to you by randos unless you trust them or review it yourself

I use this constantly, it just plays music by file name, specifically matching *NAME* with case-insensitivity. Requires bash 4.something.

# play -ln SONGS ...
# -l don't shuffle
# -n dry run
mpv_args="--no-audio-display --no-resume-playback \
          --msg-level=all=status --term-osd-bar"
shopt -s globstar nullglob nocaseglob

shuffle=true
dry=false
while [[ "$1" == -* ]]; do
    if [[ "$1" == "-l" ]]; then 
        shuffle=false
    elif [[ "$1" == "-n" ]]; then
        dry=true
    fi

    shift 1
done

if [[ "$shuffle" == true ]]; then
    mpv_args="--shuffle $mpv_args"
fi

songs=()
while [[ "$#" != 0 ]]; do
    songs+=( ~/music/**/**/*"$1"*.* ) # change this to match your music directory layout
    shift 1                                               # could probably use find instead
done

if [[ "$dry" == true ]]; then
    if [[ "$shuffle" == true ]]; then
        printf "Shuffle mode is on\n"
    fi

    for song in "${songs[@]}"; do
        printf "$song\n"
    done
  
    exit
fi

if [[ ${#songs[@]} != 0 ]]; then
    mpv $mpv_args "${songs[@]}"
fi

I make no claims to the quality of this but it works!

15 comments

  1. [2]
    spit-evil-olive-tips
    (edited )
    Link
    I've switched to fish shell on every machine I own and it's wonderful. It has features bash can't even dream of, like closures when defining functions. Why would you ever need a closure when shell...

    I've switched to fish shell on every machine I own and it's wonderful.

    It has features bash can't even dream of, like closures when defining functions. Why would you ever need a closure when shell scripting, you ask?

    As one example, we use Docker at $dayjob, so I sometimes connect remotely to various Docker hosts, using syntax like:

    docker -H 10.10.1.1 ps
    

    Where 10.10.1.1 can be any of a number of hosts. I made a shortcut to simplify that, using closures to define the IP:

    set -g IPS 1.1 2.2 3.3
    for ip in $IPS
        function d-$ip --inherit-variable ip
            docker -H 10.10.$ip $argv
        end
    end
    

    Boom, shell aliases defined programmatically, based on a loop variable.

    There's much more I love about fish - the history support is another big one. If you've ever used Ctrl-R to search through your bash history, imagine that but baked deeper in. Type a partial command and hit up to start scrolling through commands in your history that contained that snippet.

    12 votes
    1. giodamelio
      Link Parent
      I have been on fish for a few years too, and it makes writing shell scripts much better. I have a bunch of scripts, but I think one of my favorite/most used is my network command. Basically just a...

      I have been on fish for a few years too, and it makes writing shell scripts much better. I have a bunch of scripts, but I think one of my favorite/most used is my network command. Basically just a quick overview of your network connection. It does require a little program I wrote to make it pretty, but that would be really easy to remove. Also jq to show the GeoLocation.

      function network
          # Print network interfaces
          ip -4 -o a | cut -d ' ' -f 2,7 | cut -d '/' -f 1 | column -t | little_boxes --title "Network Interfaces"
      
          # Print public ip addresses
          printf 'ipv4 %s\nipv6 %s\n' (curl --silent --ipv4 icanhazip.com) (curl --silent --ipv6 icanhazip.com)  | little_boxes --title "Public IP Address"
      
          # Print location if "jq" command is present
          if command --search jq > /dev/null do
              printf 'location %s\n' (curl -s http://ipinfo.io/ | jq -r '.city + " " + .country') | little_boxes --title "GeoIP"
          end
      
          # Do a quick ping to google to test internet connectivity
          ping -c 4 -i 0.2 google.com | little_boxes --title "Ping Google"
      end
      
      3 votes
  2. [3]
    Comment deleted by author
    Link
    1. pseudolobster
      Link Parent
      Huh, today I learned my terminal supports the ANSI blink code (\e[5m). Time to make a really annoying MOTD using this newfound knowledge.

      Huh, today I learned my terminal supports the ANSI blink code (\e[5m). Time to make a really annoying MOTD using this newfound knowledge.

      4 votes
    2. apoctr
      Link Parent
      Psst, wrap your code in triple backticks. ``` echo hello world ``` formats as echo hello world

      Psst, wrap your code in triple backticks.

      ```
      echo hello world
      ```

      formats as

      echo hello world
      
      3 votes
  3. [3]
    spit-evil-olive-tips
    Link
    A quick little password generator that I have aliased to pw: python3.6 -c "import secrets; print(secrets.token_urlsafe(16))" Requires the secrets module added in 3.6 that's a useful wrapper around...

    A quick little password generator that I have aliased to pw:

    python3.6 -c "import secrets; print(secrets.token_urlsafe(16))"
    

    Requires the secrets module added in 3.6 that's a useful wrapper around os.urandom. 16 bytes / 128 bits of entropy, encoded using the URL-safe base64 encoding so it'll have uppercase, lowercase, and numbers. Sometimes a punctuation character, sometimes not, so if you're generating a password for a site which requires one you might need to repeat it a few times.

    3 votes
    1. [2]
      apoctr
      (edited )
      Link Parent
      This can be done in shell fairly easily too: base64 /dev/urandom | head -c 16. At least I think that's an equivalent, I'm not too sure of these things. 103 day old edit: see Emerald_Knight's...

      This can be done in shell fairly easily too: base64 /dev/urandom | head -c 16. At least I think that's an equivalent, I'm not too sure of these things.

      103 day old edit: see Emerald_Knight's comment below for a 'URL safe' version :)
      Also, a version for OpenBSD (a little long, there's no -c for head):

      b64encode /dev/urandom pw | head -n2 | tail -n1 | sed -e 's/\//_g; s/+/-/g' | cut -c 1-16
      
      6 votes
      1. Emerald_Knight
        (edited )
        Link Parent
        I'm way late to the party on this, but I just wanted to clarify on this topic: The Python secrets.token_urlsafe() function and the Linux base64 command have slightly differing character sets. Both...

        I'm way late to the party on this, but I just wanted to clarify on this topic:

        The Python secrets.token_urlsafe() function and the Linux base64 command have slightly differing character sets. Both use base64 encoded bytes, but the Linux base64 uses the standard 62 alphanumeric characters with + and / making up the final 2 characters, whereas the Python secrets.token_urlsafe() also uses the standard 62 alphanumeric characters but uses - and _ as the final 2 characters instead. This is important because the Python solution is inherently safe for inserting directly into a URL as a token (hence, the function is called token_urlsafe()), whereas the pure shell solution uses the base64 standard that pretty much everyone else uses which ensures interoperability.

        They're basically equivalent, but just different enough that it can matter. For any system where you're just using it as a password and the website doesn't disallow those particular special characters, they may as well be considered identical.

        If, however, you're concerned about them being different, it's a simple matter of extending your existing solution:

        base64 /dev/urandom | head -c 16 | sed 's/\//_/g' | sed 's/+/-/g'
        
        2 votes
  4. [2]
    Emerald_Knight
    (edited )
    Link
    This is a rather old thread, but I have a few helpful bash aliases I have set up that I thought I'd share, with some annotations to explain them: # Miscellaneous helpers. alias stripnewline="xargs...

    This is a rather old thread, but I have a few helpful bash aliases I have set up that I thought I'd share, with some annotations to explain them:

    # Miscellaneous helpers.
    alias stripnewline="xargs echo -n"
    alias savetoclipboard="xclip -selection clipboard"
    alias ssh="ssh -i ~/.ssh/my_private_key"
    alias execeach="xargs -n1 -I {}"
    
    # Login retrieval from LastPass, saves my named passwords to clipboard.
    # Useful for when prompted for a password while using the command line, e.g. when doing a "git push".
    alias lpassget="lpass show -cp"
    alias gitpass="lpassget GitHub"
    alias sshpass="lpassget SSH"
    
    # Quickly copy my external or internal IP address to the clipboard.
    # Useful for when I need to update firewall settings or other configuration details.
    alias publicip="curl -s https://checkip.amazonaws.com/ | stripnewline | savetoclipboard"
    alias privateip="hostname -I | stripnewline | savetoclipboard"
    
    # General file I/O helpers.
    alias grepmatch="grep -E"
    alias grepinvertmatch="grep -vE"
    
    # File safety.
    alias removesymlinks="recursiveRemoveSymlinks"
    alias saferm="safeRemove"
    
    # Git tag management.
    # Example, delete tags "v0.1.2" and "v0.2.0": "gitmatchingtags v0.1.2 v0.2.0 | gitremovetags"
    # Example, delete all tags except those in the "v3" series and "v2.5.9": "gitnonmatchingtags v2.5.9 v3 | gitremovetags"
    alias gitmatchingtags="getMatchingGitTags"
    alias gitnonmatchingtags="getNonMatchingGitTags"
    alias gitlocalremove="git tag --delete"
    alias gitremoteremove="git push --delete origin"
    alias gitremovetags="execeach bash -lic 'gitlocalremove {}; gitremoteremove {};'"
    
    # Helper method, joins all arguments with a "|" to allow matching multiple patterns simultaneously.
    constructGrepMultipleMatchPattern() {
        tagpattern="$1"
        shift 1
    
        for target in "$@"; do
                tagpattern="$tagpattern|$target"
        done
    
        echo "$tagpattern"
    }
    
    # Grabs all git tags matching any of the provided patterns.
    getMatchingGitTags() {
        tagpattern=$(constructGrepMultipleMatchPattern "$@")
        git tag --list | grepmatch "$tagpattern"
    }
    
    # Grabs all git tags not matching any of the provided patterns.
    getNonMatchingGitTags() {
        tagpattern=$(constructGrepMultipleMatchPattern "$@")
        git tag --list | grepinvertmatch "$tagpattern"
    }
    
    # Finds and deletes all symbolic links nested within the target directory.
    # Does not allow calling without arguments to avoid inadvertently defaulting to the current directory.
    recursiveRemoveSymlinks() {
        target="$1"
        if [ "$target" == "" ]; then
                echo "Missing required path argument."
        else
                find "$target" -type l -delete
        fi
    }
    
    # Removes all symbolic links in the target directories before deleting the directories themselves.
    # Useful for avoiding inadvertently deleting symlinked files when trying to delete a directory.
    safeRemove() {
        for target in "$@"; do
            removesymlinks "$target"
        done
    
        rm -rf "$@"
    }
    

    The saferm one is really useful for me in production environments where symlinks are set up inside of individual release directories and point to shared configuration files or multimedia assets. Without saferm, you have to ensure that you remove all symlinks manually before invoking rm -rf on the released git tag, which is obviously error prone. It also ensures that I don't have to unlink each symlink by hardcoding them individually inside of a script, instead finding all of them dynamically for me.

    Most of these are just there to reduce the mental overhead associated with remembering what the exact commands and sequences of those commands are and the arguments and options I need to pass to them, and to reduce the amount of repetition involved in typing them out. When not a massive headache, I also like to try to make my aliases composable through piping.

    I'm a fairly inexperienced shell scripter, to be honest, but I try to improve what I have over time. Even my worst scripts and aliases still make my workload a lot easier to manage :)

    3 votes
    1. Emerald_Knight
      Link Parent
      I just found a new one that's really helpful: alias treediff="git diff --no-index" # Usage: treediff directory1/ directory2/ This alias takes advantage of git diff functionality to directly...

      I just found a new one that's really helpful:

      alias treediff="git diff --no-index"
      # Usage: treediff directory1/ directory2/
      

      This alias takes advantage of git diff functionality to directly compare two directory structures for any differences, regardless of whether or not they're tracked by git. The drawbacks of this are that 1. it requires git, which not everyone needs, and that 2. files that shouldn't really be compared, e.g. those under .git/, will end up being diff'd. Still, I found it pretty handy :)

      1 vote
  5. uselessabstraction
    Link
    I run Herbstluftwm as my window manager, and like to keep the color scheme in sync with what I have defined in my Xresources file. I wrote this little nugget to pull the colors from xrdb and...

    I run Herbstluftwm as my window manager, and like to keep the color scheme in sync with what I have defined in my Xresources file. I wrote this little nugget to pull the colors from xrdb and define local variables which can be used throughout my autostart script.

    # IMPORT COLORS ---------------------------------------------------------------
    xrdb -load ~/.config/Xresources
    names=(black red green gold blue purple teal silver gray pink lime yellow sky magenta aqua white)
    for i in {0..15}
    do
         v=`xrdb -query | gawk '/*.color'"$i":'/ { print $2 }'`
         eval "${names[$i]}=$v";
    done
    foreground=`xrdb -query | gawk '/*.foreground/ { print $2 }'`
    background=`xrdb -query | gawk '/*.background/ { print $2 }'`
    

    There's probably a better way, but this does the job for me.

    2 votes
  6. [3]
    Zekka
    Link
    Hey, does anyone have a script that I can invoke like this? withssh -L 8080:remote:80 <command to run locally> I use SSH port forwarding a lot and would love a reliable script that lets me open an...

    Hey, does anyone have a script that I can invoke like this?

    withssh -L 8080:remote:80 <command to run locally>
    

    I use SSH port forwarding a lot and would love a reliable script that lets me open an SSH session only for the duration of another command.

    1 vote
    1. [3]
      Comment deleted by author
      Link Parent
      1. [2]
        Zekka
        Link Parent
        Cool, will this kill my SSH session when bash is done? (assume the new command doesn't fork a background process or something)

        Cool, will this kill my SSH session when bash is done? (assume the new command doesn't fork a background process or something)

  7. [2]
    river
    Link
    $ cat ~/bin/myip #!/bin/bash curl -s https://api.ipify.org/ && echo
    $ cat ~/bin/myip 
    #!/bin/bash
    curl -s https://api.ipify.org/ && echo
    
    1 vote
    1. river
      Link Parent
      #!/bin/bash ram() { PAGE_SIZE=$(getconf PAGE_SIZE) for pid in /proc/*/ do if [ -a "$pid/statm" ] then MEM=`cat "$pid/statm" | awk "{print \\$2*\$PAGE_SIZE}"` if [ $MEM != 0 ] then CMD=`cat -t...
      #!/bin/bash
      
      ram() {
          PAGE_SIZE=$(getconf PAGE_SIZE)
          
          for pid in /proc/*/
          do
              if [ -a "$pid/statm" ]
              then
                  MEM=`cat "$pid/statm" | awk "{print \\$2*\$PAGE_SIZE}"`
                  if [ $MEM != 0 ]
                  then
                      CMD=`cat -t "$pid/cmdline" | sed -e 's/\^@/ /g'`
                      echo "$MEM" "$CMD"
                  fi
              fi
          done
      }
      
      ram | sort -n | awk '{ sum=$1 ; hum[1024**3]="Gb";hum[1024**2]="Mb";hum[1024]="Kb"; for (x=1024**3; x>=1024; x/=1024){ if (sum>=x) { $1=""; printf "%.2f%s%s\n",sum/x,hum[x],$0;break } }}'
      
      # Unit conversion trick taken from: http://ubuntuforums.org/showthread.php?t=1795681
      
      1 vote