Because who doesn't love shell tricks.
For far more serious tricks see the DevOps-Bash-tools repo.
- System & Shell
- Processes
- Dates
- Files & Strings
- Generate ASCII Art
- Number Lines
- Copy to Both Clipboard and Stdout simultaneously
- Squeeze Out Multiple Blank Lines
- Reverse a String
- Reverse the lines of a file
- Shuffle Lines of a File
- Split Big File into 10MB chunks
- Show Files Open by a Process
- Generate a Random Password
- Base64 Secrets to avoid dodgy characters
- Deduplicate Lines Using Awk
- Find Lines in a File present in Other Files
- Find Lines in a File not present in Other Files
- Removing Files with Difficult Special Characters
- Network
- Disk
- Mac
- More Resources
nprocecho $PATH | tr ':' '\n'script logfile.txtYou can wrap part or all of your script in braces and use standard output redirection to a logfile:
{
echo "capturing this output"
} > output.log 2>&1To capture all stdout from this point down in the script to a logfile eg. output.log,
as well as still print it to the terminal:
exec > >(tee output.log)Sometimes you want the stderr to go the terminal as that's where you log progress of what the script is doing while collecting the output data.
To have stderr also be logged into the output.log file:
exec > >(tee output.log) 2>&1This is using the Bash trick of having a process as an input or output file descriptor, similar to Use Process Outputs Like Files.
If your shell history is vanilla without timestamps:
history | awk '{ a[$2]++ } END { for(i in a) { print a[i] " " i } }' | sort -rn | head -n 100If you're using timestamped shell history:
history | awk '{ a[$4]++ } END { for(i in a) { print a[i] " " i } }' | sort -rn | head -n 100 ps -ef | awk '$3 == 1 {print}'Access command outputs like files without using temp files.
The <(...) Bash syntax returns a temporary process file descriptor.
some_command_expecting_a_file <(some_command_printing_to_stdout)
For example, compare two unsorted files without using temp files:
diff <(sort "$file1") <(sort "$file2")Seconds since Unix birth 1st Jan 1970:
date '+%s'date -d @1741942727brew install figletfiglet "Hari Sekhon" _ _ _ ____ _ _
| | | | __ _ _ __(_) / ___| ___| | _| |__ ___ _ __
| |_| |/ _` | '__| | \___ \ / _ \ |/ / '_ \ / _ \| '_ \
| _ | (_| | | | | ___) | __/ <| | | | (_) | | | |
|_| |_|\__,_|_| |_| |____/ \___|_|\_\_| |_|\___/|_| |_|
cat -nless -Nnltee to both a command to copy to clipboard as well as stdout.
Use /dev/stdout for further pipeline processing, not /dev/tty, as the latter outputs directly to the terminal.
The copy_to_clipboard.sh script from DevOps-Bash-tools works on both Linux and Mac:
echo test | tee >("copy_to_clipboard.sh") /dev/stdoutUseful to remove multiple blank lines between paragraphs in text replacements like shorten_text_selection.scpt.
cat -secho "testing" | revMore portable than tac:
tail -r file.txtshuf file.txtsplit -b 10M bigfile split_lsof -p "$(pidof bash)"brew install pwgenGenerates a bunch of passwords to choose from
pwgenOr generate a single one of length 20 chars and avoid any ambiguous chars:
pwgen -1 -s -B 20Useful in things like GitHub Actions CI/CD for safer character handling.
base64use --decode switch instead of -d for better portability between your Mac and Linux:
base64 --decodeIf you have an input such as <id> <content> and want to deduplicate lines with the same ID by comparing the
first field, this is simple and powerful:
awk '!seen[$1]++'You could of course deduplicate entire lines by just using $0 as the associative array index:
awk '!seen[$0]++'This uses the trick of populating an associative array using the field as the index and an ++ incrementer to just
create a value (1) to signal existance.
This follows standard computer science post-increment behaviour such that the first time it does this it returns nothing
since it didn't already exist and so the leading negation will allow the first instance through and print the line, but
subsequent instances where the value used for the index, $1 for the ID field or the $0 for the entire line,
actually returns an existing value from the last time that index value was seen and incremented into existence, it'll
match the negation and suppress those lines.
I used this to find Spotify tracks that are in one of my blacklists as I download all my playlists to Git revision controlled backups and use scripts to remove songs I've already checked (HariSekhon/Spotify-Playlists).
-F- for fixed string-x- to ensure we match whole lines-h- to not return the prefix of "$fileN: " before each line printed as we just want the line itself-f- take the lines as patterns from the first file and look for them in all subsequent file arguments
grep -Fxhf "$patterns_file" "$file1" "$file2" ...Pipe to sort -u to deduplicate if something is found in more than one file.
Same as above just grep -v it:
grep -Fvxhf "$patterns_file" "$file1" "$file2" ...Example:
I use this to find tracks in my Spotify playlists that are not in
Spotify's Like Songs so I can quickly add them programmatically.
The spotify/<files> paths contain the downloaded track URIs, while the top level contains the
Artist - Track human readable downloads.
Find the URIs in a playlist that are not in the Liked Songs, which we use as the first arg, the patterns file:
grep -Fvxhf "spotify/Liked Songs" "spotify/$playlist"and then pipe it to one of my many spotify scripts that can be found in DevOps-Bash-tools to programmatically 'Like' all of the straggers found.
First exclude local tracks which can't be Liked, and then pipe it to spotify_uri_to_name.sh to see the track names:
grep -Fvxhf "spotify/Liked Songs" "spotify/Smooth Hip-Hop 😎" |
grep -v "^spotify:local:" |
spotify_uri_to_name.shOnce you've reviewed the songs to be Liked, pipe it to spotify_set_tracks_uri_to_liked.sh instead of
spotify_uri_to_name.sh, to actually programmatically Like them:
grep -Fvxhf "spotify/Liked Songs" "spotify/Smooth Hip-Hop 😎" |
grep -v "^spotify:local:" |
spotify_set_tracks_uri_to_liked.shSometimes you're coding and generate filenames with awful characters such as newlines, unicodes or emojis that can be either difficult or downright dangerous to try to remove on the shell command line.
In this case, cheat - outsource the deletion to an interactive tool like your GUI File Browser such as macOS Finder or on the command line using an interactive tool like Midnight Commander.
Install Midnight Commander on macOS using Homebrew:
brew install mcStart Midnight Commander:
mcScroll down to the file or directory and then hit Esc - 8 to prompt deletion.
Esc - fn - 0 to exit on macOS.
These tricks use publicly available services to return what your actual public IP address is after all NAT translation ie. what your IP is actually seen as by other computers on the internet.
curl ifconfig.cocurl checkip.amazonaws.comor using just DNS:
dig +short myip.opendns.com @resolver1.opendns.comWebsites that return the IP without a trailing newline:
curl ifconfig.mecurl https://ipinfo.iocurl ident.meProgrammatically useful with more details returned in JSON:
curl ifconfig.co/jsonShow your local listening ports:
netstat -lntpunc -zv localhost 22localhost [127.0.0.1] 22 (ssh) open
Checks every 3 seconds, makes an audible bell, and exits when a host comes online:
ping -i 3 -o -a "$host"python3 -m http.server 8000brew install nethogssudo nethogsThis searches only the current directory and only on the current partition, not sub-mount-points.
du -max . | sort -k1n | tail -n 1000See also for a GUI alternative:
- Disk Inventory X - Mac
- KDirStat - Linux
- WinDirStat - Windows
mkdir /tmp/ramdisk &&
mount -t tmpfs tmpfs /tmp/ramdisk -o size=1024mFor cross-platform portability of your commands and scripts since GNU CoreUtils have better features than their BSD counterparts, and less bugs in advanced usage in my experience.
brew install coreutilsThis will install GNU Core Utils prefixed with g to not clash with the native BSD counterparts.
You can wrap them in functions (I do this in scripts and libraries eg. DevOps-Bash-tools:
grep(){
command ggrep "$@"
}
sed(){
command sed "$@"
}Now when the script calls grep it gets the better functional version for cross-platform compatibility between Mac and Linux.