#!/usr/bin/env bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)select-table 3.3.1 16.03.2026 (c)2026 Standby.cloud # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ # # This script can be used free of charge. Use it as is or customize as needed. It is not guaranteed to be # error free and you will not be reimbursed for any damage it may cause. # #@ Reads a tab separated file and pretty prints it and ask user to select a value. #@ #@Usage: select-table [options] [keys] #@ Options: #@ -h, --help : Displays helptext. #@ -v, --version : Displays the version of the script. #@ -i, --import : Read from a file ( = filename) - if not specified, read from stdin. #@ -e, --export : Write result to file ( = filename) - mandatory to set. #@ -g, --green : If is the same as column, display it green. #@ -y, --yellow : If is the same as column, display it yellow. #@ -r, --red : If is the same as column, display it red. #@ -d, --darkgray : If is the same as column, display it gray. #@ -b, --bold : If is the same as column, display it bold. #@ -p, --page : Use pagination. Diplay only of lines. #@ Keys: Optional - Select the keys you want to display. Use a colon (or tab) separated string for keys. #@ #@Examples: #@ #@ printf "cmd\tvers\tos\njq\t1.6\tlinux\nrclone\t1.51.0\tlinux\n" | select-table --export result.tsv #@ #@ sel cmd vers os #@ --- ------ ------ ----- #@ 001 jq 1.6 linux #@ 002 rclone 1.51.0 linux #@ #@ Please select a number between 1 and 2 or 'q' to quit: # # Exit codes: # 01: Unknown or wrong parameter. # 02: Input is invalid. # 03: No value selected. # 04: Error while writing to result file. # 99: User interrupt. # # Update history: # # V 3.0.0 21.04.2021 New version # V 3.1.0 05.06.2023 New copyright # V 3.2.0 12.08.2024 New minor version # V 3.2.1 14.11.2024 More options # V 3.3.0 19.01.2026 Revised with support of Claude Code # V 3.3.1 16.03.2026 Optimized: replaced backticks with $(); [ ] with [[ ]]; # echo|grep with [[ =~ ]]; replaced let with (( )); # arithmetic with (( )) instead of $((...)) # # Find executable bash library and source it lib=$(command -v lib.bash 2>/dev/null) if [[ -n "$lib" ]]; then source "$lib" else progdir=$(dirname "$0") if [[ -r "${progdir}/lib.bash" ]]; then source "${progdir}/lib.bash" else echo "Unexpected error: Unable to locate bash library 'lib.bash'." exit 1 fi fi # Do extra cleanup function ExtraCleanup() { filecheck -rm "${scratchfile}.org" filecheck -rm "${scratchfile}.wrk" } # Check if string is a number and within min and max function CheckNumber() { local number="${1}" local min="${2}" local max="${3}" # Use bash regex instead of echo|grep subshell if [[ ! "$number" =~ ^[0-9]+$ ]]; then printf '\n' return fi if [[ -n "$min" ]] && (( number < min )); then printf '\n' return fi if [[ -n "$max" ]] && (( number > max )); then printf '\n' return fi printf '%s\n' "$number" } function Select() { local result="" local lstart=0 local lend=0 local stat=0 if (( pagination > 0 )); then lstart=1 lend=$pagination else lstart=1 lend=$max fi while [[ -z "$result" ]]; do if (( pagination > 0 )); then printf "\nPage: %d\n\n" "$page" printf '%s\n' "$header" > "$scratchfile" sed -n "${lstart},${lend}p" "${scratchfile}.wrk" >> "$scratchfile" else printf "\n" printf '%s\n' "$header" > "$scratchfile" cat "${scratchfile}.wrk" >> "$scratchfile" fi print-table --output table --import "$scratchfile" $redstr $yellowstr $greenstr $graystr $boldstr if (( pagination > 0 )); then if (( page == 1 )); then printf "\nPlease select a number between %d and %d, 'n' for next page or 'q' to quit: " "$lstart" "$lend" elif (( lend == max )); then printf "\nPlease select a number between %d and %d, 'b' for back or 'q' to quit: " "$lstart" "$lend" else printf "\nPlease select a number between %d and %d, 'n' for next page, 'b' for back or 'q' to quit: " "$lstart" "$lend" fi else printf "\nPlease select a number between %d and %d or 'q' to quit: " "$lstart" "$lend" fi # Read input from user read -r inp < /dev/tty if (( pagination > 0 )); then if (( page == 1 )); then if (( lend < max )) && [[ "$inp" == "n" || "$inp" == "next" ]]; then (( lstart = page * pagination + 1 )) (( page++ )) (( lend = page * pagination )) (( lend > max )) && lend=$max inp="" fi else if [[ "$inp" == "b" || "$inp" == "back" ]]; then (( page-- )) (( lstart = page * pagination - pagination + 1 )) (( lend = page * pagination )) inp="" elif (( lend < max )) && [[ "$inp" == "n" || "$inp" == "next" ]]; then (( lstart = page * pagination + 1 )) (( page++ )) (( lend = page * pagination )) (( lend > max )) && lend=$max inp="" fi fi fi if [[ "$inp" == "q" || "$inp" == "quit" ]]; then result="quit" stat=3 elif [[ -n "$inp" ]]; then result=$(CheckNumber "$inp" "${lstart}" "${lend}") if [[ -n "$result" ]]; then (( result++ )) sed -n "${result}p" "${scratchfile}.org" > "$resultfile" stat=$? if [[ $stat -gt 0 ]]; then stat=4 fi else printf "Invalid input '%s'.\n" "$inp" fi fi done return $stat } # Preset page=0 pagination=0 param="" filename="" resultfile="" redstr="" greenstr="" yellowstr="" graystr="" boldstr="" header="" # Check parameters: Loop until all parameters are used up while [[ $# -gt 0 ]]; do pname="${1}" case "$pname" in -v | --version) shift showversion=true ;; -h | --help) shift showhelp=true ;; -p | --page) shift if [[ -n "$1" ]]; then pagination=$(CheckNumber "${1}" "1" "100") if [[ -z "$pagination" ]]; then errstr="Please specify a number between 1 and 100 after parameter '$pname'." fi shift else errstr="Please specify a number after parameter '$pname'." fi ;; -r | --red) shift if [[ -n "$1" ]]; then redstr="$pname ${1}" shift else errstr="Please specify a string after parameter '$pname'." fi ;; -g | --green) shift if [[ -n "$1" ]]; then greenstr="$pname ${1}" shift else errstr="Please specify a string after parameter '$pname'." fi ;; -y | --yellow) shift if [[ -n "$1" ]]; then yellowstr="$pname ${1}" shift else errstr="Please specify a string after parameter '$pname'." fi ;; -d | --darkgray) shift if [[ -n "$1" ]]; then graystr="$pname ${1}" shift else errstr="Please specify a string after parameter '$pname'." fi ;; -b | --bold) shift if [[ -n "$1" ]]; then boldstr="$pname ${1}" shift else errstr="Please specify a string after parameter '$pname'." fi ;; -i | --import) shift if [[ -n "$1" ]]; then if [[ -z "$filename" ]]; then filename="$1" if [[ ! -r "$filename" ]]; then errstr="Can't read from filename '$filename'." fi else errstr="Option '$pname' used more then once." fi shift else errstr="Please specify a filename after parameter '$pname'." fi ;; -e | --export) shift if [[ -n "$1" ]]; then if [[ -z "$resultfile" ]]; then resultfile="$1" result=$(filecheck -w "$resultfile") if [[ -z "$result" ]]; then errstr="Filename '$resultfile' not writable." fi else errstr="Option '$pname' used more then once." fi shift else errstr="Please specify a filename after parameter '$pname'." fi ;; *) shift if [[ "$pname" == -* ]]; then errstr="Unknown option '$pname'." else if [[ -z "$errstr" ]]; then if [[ -z "$param" ]]; then param="$pname" else errstr="Keys were already specified '$param'. Unknown additional parameter: '$pname'." fi fi fi ;; esac done # Check if filename is empty and file exists if [[ -z "$resultfile" ]]; then errstr="Please specify a file with option '--export' to store result." fi # Display help or error message DisplayHelp ### Main # Check if filename is empty and create workfile if [[ -z "$filename" ]]; then if [[ ! -t 0 ]]; then # We have a stream print-table --quiet --output tsv "$param" < /dev/stdin > "${scratchfile}.org" exitcode=$? else exitcode=2 errormsg "" $exitcode "($progstr) Please specify a file with tab separated values (tsv)." exit $exitcode fi else # Create a workfile without empty lines print-table --quiet --import "$filename" --output tsv "$param" > "${scratchfile}.org" exitcode=$? fi if [[ $exitcode -eq 0 ]]; then i=0 while IFS= read -r line; do if (( i == 0 )); then header=$(printf "sel\t%s" "$line") else printf "%03d\t%s\n" "$i" "$line" >> "${scratchfile}.wrk" fi (( i++ )) done < "${scratchfile}.org" (( max = i - 1 )) # Number of data lines if (( max <= pagination )); then pagination=0 else page=1 fi Select exitcode=$? else exitcode=2 errormsg $exitcode "($progstr) Invalid input (not in tsv format). Exiting." fi # Cleanup and exit Cleanup exit $exitcode