| 1 | #!/bin/bash |
| 2 | # Desc: Aligns path elements with spaces |
| 3 | # Usage: palign.sh [path] |
| 4 | # find . -type f | palign.sh |
| 5 | # Input: stdin paths newline-delimited paths (forward-slash delimited) |
| 6 | # psargs paths word-delimited paths (forward-slash delimited) |
| 7 | # Output: stdout |
| 8 | # Depends: Bash 5.1.16 |
| 9 | # Version: 0.0.1 |
| 10 | |
| 11 | yell() { echo "$0: $*" >&2; } # print script path and all args to stderr |
| 12 | die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status |
| 13 | must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails |
| 14 | read_stdin() { |
| 15 | # Desc: Consumes stdin; outputs as stdout lines |
| 16 | # Input: stdin (consumes) |
| 17 | # Output: stdout (newline delimited) |
| 18 | # return 0 stdin read |
| 19 | # return 1 stdin not present |
| 20 | # Example: printf "foo\nbar\n" | read_stdin |
| 21 | # Depends: GNU bash (version 5.1.16), GNU Coreutils 8.32 (cat) |
| 22 | # Version: 0.1.1 |
| 23 | # Attrib: Steven Baltakatei Sandoval (2024-01-29). reboil.com |
| 24 | local input_stdin output; |
| 25 | |
| 26 | # Store stdin |
| 27 | if [[ -p /dev/stdin ]]; then |
| 28 | input_stdin="$(cat -)" || { |
| 29 | echo "FATAL:Error reading stdin." 1>&2; return 1; }; |
| 30 | else |
| 31 | return 1; |
| 32 | fi; |
| 33 | |
| 34 | # Store as output array elements |
| 35 | ## Read in stdin |
| 36 | if [[ -n $input_stdin ]]; then |
| 37 | while read -r line; do |
| 38 | output+=("$line"); |
| 39 | done < <(printf "%s\n" "$input_stdin") || { |
| 40 | echo "FATAL:Error parsing stdin."; return 1; }; |
| 41 | fi; |
| 42 | |
| 43 | # Print to stdout |
| 44 | printf "%s\n" "${output[@]}"; |
| 45 | |
| 46 | return 0; |
| 47 | }; # read stdin to stdout lines |
| 48 | read_psarg() { |
| 49 | # Desc: Reads arguments; outputs as stdout lines |
| 50 | # Input: args |
| 51 | # Output: stdout (newline delimited) |
| 52 | # Example: read_psarg "$@" |
| 53 | # Depends: GNU bash (version 5.1.16) |
| 54 | # Version: 0.0.1 |
| 55 | local input_psarg output; |
| 56 | |
| 57 | # Store arguments |
| 58 | if [[ $# -gt 0 ]]; then |
| 59 | input_psarg="$*"; |
| 60 | fi; |
| 61 | |
| 62 | # Store as output array elements |
| 63 | ## Read in positional arguments |
| 64 | if [[ -n $input_psarg ]]; then |
| 65 | for arg in "$@"; do |
| 66 | output+=("$arg"); |
| 67 | done; |
| 68 | fi; |
| 69 | |
| 70 | # Print to stdout |
| 71 | printf "%s\n" "${output[@]}"; |
| 72 | }; # read positional argument to stdout lines |
| 73 | calculate_max_lengths() { |
| 74 | # Calculate maximum lengths of each hierarchical level |
| 75 | # Input: psargs paths |
| 76 | # Output: stdout ints path element lengths (single line of space-separated ints) |
| 77 | local -a paths=("$@"); |
| 78 | local lengths; |
| 79 | |
| 80 | for path in "${paths[@]}"; do |
| 81 | IFS='/' read -r -a parts <<< "$path"; |
| 82 | # Analyze path element lengths |
| 83 | for i in "${!parts[@]}"; do |
| 84 | # Save max path element lengths for previously analyzed paths |
| 85 | # Note: $(( condition ? value_true : value_false )) |
| 86 | # condition: ${#parts[$i]} > ${lengths[$i]:-0} is path element longer than previously seen corresponding paths? |
| 87 | # value_true: ${#parts[$i]} save new max length |
| 88 | # value_false ${lengths[$i]:-0} use previous value (or init with 0 if never seen before) |
| 89 | lengths[$i]=$(( ${#parts[$i]} > ${lengths[$i]:-0} ? ${#parts[$i]} : ${lengths[$i]:-0} )); |
| 90 | done; |
| 91 | done; |
| 92 | echo "${lengths[@]}"; |
| 93 | }; # Get maximum lengths of path elements as space-separated ints |
| 94 | align_paths() { |
| 95 | # Align paths |
| 96 | # Input: stdin |
| 97 | # Output: stdout |
| 98 | # Depends: calculate_max_lengths() |
| 99 | local -a paths lengths; |
| 100 | |
| 101 | # Read stdin |
| 102 | while read -r line; do |
| 103 | paths+=("$line"); |
| 104 | done < <(read_stdin); |
| 105 | |
| 106 | IFS=" " read -r -a lengths <<< "$(calculate_max_lengths "${paths[@]}")"; |
| 107 | |
| 108 | for path in "${paths[@]}"; do |
| 109 | IFS='/' read -r -a parts <<< "$path"; |
| 110 | formatted_path=""; |
| 111 | for i in "${!parts[@]}"; do |
| 112 | if [[ $i -gt 0 ]]; then |
| 113 | formatted_path+="/"; |
| 114 | fi; |
| 115 | formatted_path+="${parts[$i]}"; |
| 116 | if [[ $i -lt $((${#parts[@]} - 1)) ]]; then |
| 117 | formatted_path+=$(printf "%*s" $((${lengths[$i]} - ${#parts[$i]} + 1)) ""); |
| 118 | fi; |
| 119 | done; |
| 120 | echo "$formatted_path"; |
| 121 | done; |
| 122 | }; # Function to format and align paths |
| 123 | main() { |
| 124 | { read_stdin; read_psarg "$@"; } | align_paths; |
| 125 | }; # Call the function with the file paths |
| 126 | main "$@"; |
| 127 | |
| 128 | # Author: Steven Baltakatei Sandoval |
| 129 | # License; GPLv3+ |