+#!/bin/bash
+# Desc: Aligns path elements with spaces
+# Usage: palign.sh [path]
+# find . -type f | palign.sh
+# Input: stdin paths newline-delimited paths (forward-slash delimited)
+# psargs paths word-delimited paths (forward-slash delimited)
+# Output: stdout
+# Depends: Bash 5.1.16
+# Version: 0.0.1
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+read_stdin() {
+ # Desc: Consumes stdin; outputs as stdout lines
+ # Input: stdin (consumes)
+ # Output: stdout (newline delimited)
+ # return 0 stdin read
+ # return 1 stdin not present
+ # Example: printf "foo\nbar\n" | read_stdin
+ # Depends: GNU bash (version 5.1.16), GNU Coreutils 8.32 (cat)
+ # Version: 0.1.1
+ # Attrib: Steven Baltakatei Sandoval (2024-01-29). reboil.com
+ local input_stdin output;
+
+ # Store stdin
+ if [[ -p /dev/stdin ]]; then
+ input_stdin="$(cat -)" || {
+ echo "FATAL:Error reading stdin." 1>&2; return 1; };
+ else
+ return 1;
+ fi;
+
+ # Store as output array elements
+ ## Read in stdin
+ if [[ -n $input_stdin ]]; then
+ while read -r line; do
+ output+=("$line");
+ done < <(printf "%s\n" "$input_stdin") || {
+ echo "FATAL:Error parsing stdin."; return 1; };
+ fi;
+
+ # Print to stdout
+ printf "%s\n" "${output[@]}";
+
+ return 0;
+}; # read stdin to stdout lines
+read_psarg() {
+ # Desc: Reads arguments; outputs as stdout lines
+ # Input: args
+ # Output: stdout (newline delimited)
+ # Example: read_psarg "$@"
+ # Depends: GNU bash (version 5.1.16)
+ # Version: 0.0.1
+ local input_psarg output;
+
+ # Store arguments
+ if [[ $# -gt 0 ]]; then
+ input_psarg="$*";
+ fi;
+
+ # Store as output array elements
+ ## Read in positional arguments
+ if [[ -n $input_psarg ]]; then
+ for arg in "$@"; do
+ output+=("$arg");
+ done;
+ fi;
+
+ # Print to stdout
+ printf "%s\n" "${output[@]}";
+}; # read positional argument to stdout lines
+calculate_max_lengths() {
+ # Calculate maximum lengths of each hierarchical level
+ # Input: psargs paths
+ # Output: stdout ints path element lengths (single line of space-separated ints)
+ local -a paths=("$@");
+ local lengths;
+
+ for path in "${paths[@]}"; do
+ IFS='/' read -r -a parts <<< "$path";
+ # Analyze path element lengths
+ for i in "${!parts[@]}"; do
+ # Save max path element lengths for previously analyzed paths
+ # Note: $(( condition ? value_true : value_false ))
+ # condition: ${#parts[$i]} > ${lengths[$i]:-0} is path element longer than previously seen corresponding paths?
+ # value_true: ${#parts[$i]} save new max length
+ # value_false ${lengths[$i]:-0} use previous value (or init with 0 if never seen before)
+ lengths[$i]=$(( ${#parts[$i]} > ${lengths[$i]:-0} ? ${#parts[$i]} : ${lengths[$i]:-0} ));
+ done;
+ done;
+ echo "${lengths[@]}";
+}; # Get maximum lengths of path elements as space-separated ints
+align_paths() {
+ # Align paths
+ # Input: stdin
+ # Output: stdout
+ # Depends: calculate_max_lengths()
+ local -a paths lengths;
+
+ # Read stdin
+ while read -r line; do
+ paths+=("$line");
+ done < <(read_stdin);
+
+ IFS=" " read -r -a lengths <<< "$(calculate_max_lengths "${paths[@]}")";
+
+ for path in "${paths[@]}"; do
+ IFS='/' read -r -a parts <<< "$path";
+ formatted_path="";
+ for i in "${!parts[@]}"; do
+ if [[ $i -gt 0 ]]; then
+ formatted_path+="/";
+ fi;
+ formatted_path+="${parts[$i]}";
+ if [[ $i -lt $((${#parts[@]} - 1)) ]]; then
+ formatted_path+=$(printf "%*s" $((${lengths[$i]} - ${#parts[$i]} + 1)) "");
+ fi;
+ done;
+ echo "$formatted_path";
+ done;
+}; # Function to format and align paths
+main() {
+ { read_stdin; read_psarg "$@"; } | align_paths;
+}; # Call the function with the file paths
+main "$@";
+
+# Author: Steven Baltakatei Sandoval
+# License; GPLv3+