|
#!/usr/bin/env bash |
|
|
|
# sort wrapper for saving original order of lines which have the same key values. |
|
# Copyright (c) 2018 Koichi OKADA. All rights reserved. |
|
# This script is distributed under the MIT license. |
|
|
|
(( 5 <= DEBUG )) && set -x |
|
|
|
function function_get_lines () # PAT_FUNC [FILE] |
|
# Return lines of function about declare, begin and end. |
|
{ |
|
grep -nE "^$1|^{|^}" "${@:2:1}" \ |
|
| grep -A2 -E "^[0-9]+:$1" \ |
|
| grep -o -E "^[0-9]+" |
|
} |
|
|
|
function headtail () # FIRST_LINE LAST_LINE |
|
# Split lines from FIRST_LINE to LAST_LINE. |
|
{ |
|
head -n+$2 | tail -n+$1 |
|
} |
|
|
|
function uniqex () |
|
# An alternative uniq command which is not required sort |
|
{ |
|
awk '!c[$0]++' |
|
} |
|
|
|
function show_options () |
|
{ |
|
local i |
|
echo "Options:" |
|
echo " OPT0:" ; for i in "${OPT0[@]}" ; do echo " $i"; done |
|
echo " OPT1:" ; for i in "${OPT1[@]}" ; do echo " $i"; done |
|
echo " OPT01:"; for i in "${OPT01[@]}"; do echo " $i"; done |
|
} |
|
|
|
function usage_default () #= [CMD [DISPLAY_NAME]] |
|
# Auto generate default usage from documentation |
|
# Args: |
|
# CMD : Whole name of subcommand |
|
{ |
|
local CMD="${1:-$CMD}" |
|
local DISPLAY_NAME="${2:-$CMD}" |
|
local PAT_FUNC_CMD="function +${CMD} *\( *\)" |
|
local lines src srcs |
|
|
|
readarray -t srcs < <(grep -lE "$PAT_FUNC_CMD" "${BASH_SOURCE[@]}" | tac | uniqex) |
|
[ -z "$srcs" ] && { echo "Error: function ${CMD} is not founded."; exit 1; } |
|
|
|
src="$srcs" |
|
readarray -t lines < <(function_get_lines "$PAT_FUNC_CMD" "$src") |
|
cat "$src" \ |
|
| headtail $(( lines[0] )) $(( lines[1] - 1 )) \ |
|
| sed -r -e 's/^(function +)('"$CMD"')( *\( *\))/\1'"$DISPLAY_NAME"'\3/g' \ |
|
| sed -r -e 's/^function +([^ ]+)[^#]*(#=? *(.*))?/Usage: \1 \3/g' \ |
|
-e 's/^#\?? (.*)/\1/g' |
|
show_options |
|
} |
|
|
|
function keydefparse () # KEYDEF |
|
# Parse KEYDEF parameter for -k and --key options of sort command, and update conditions for wrapping the sort command. |
|
# Returns: |
|
# TEMPLATES : templates for printf of awk. |
|
# FIELDS : fields for printf of awk |
|
# KEYCOUNT : number of sort keys |
|
# KEYDEF : KEYDEF for wrapping the sort command |
|
{ |
|
local f1 c1 opts1 f2 c2 opts2 tmp i keycount1 keycount2 |
|
shopt -q extglob; local extglob=$?; shopt -s extglob |
|
f1="${1%%,*}" |
|
[ "$1" != "${1/,/}" ] && f2="${1##*,}" |
|
opts1="${f1/#*([0-9.])/}" |
|
opts2="${f2/#*([0-9.])/}" |
|
f1="${f1:0:${#f1}-${#opts1}}" |
|
f2="${f2:0:${#f2}-${#opts2}}" |
|
[ "$f1" != "${f1/./}" ] && c1="${f1#*.}" |
|
[ "$f2" != "${f2/./}" ] && c2="${f2#*.}" |
|
f1="${f1%.*}" |
|
f2="${f2%.*}" |
|
if [[ -z "$f1" || -z "$f2" ]]; then |
|
echo "Error: KEYDEF must be given both start and stop position." |
|
exit 1 |
|
fi |
|
let keycount1=KEYCOUNT |
|
for (( i = f1; i <= f2; i++ )); do |
|
TEMPLATES+="%s," |
|
FIELDS+="\$${i}," |
|
let KEYCOUNT++ |
|
done |
|
let keycount2=KEYCOUNT-1 |
|
KEYDEF="${keycount1}${c1:+.}${c1}${opts1},${keycount2}${c2:+.}${c2}${opts2}" |
|
[ "$extglob" = "1" ] && shopt -u extglob |
|
} |
|
|
|
function optparse () # [OPTIONS ...] [ARGUMENTS ...] |
|
# Parse options of COMMAND, and update conditions for wrapping it. |
|
# Before call this function, COMMAND must be gotten options with `get_options`. |
|
# Returns: |
|
# ARGS[@] : positional parameters |
|
# OPTS[@] : options of COMMAND |
|
{ |
|
local short param KEYDEF |
|
shopt -q extglob; local extglob=$?; shopt -s extglob |
|
ARGS=() |
|
while (( 0 < $# )); do |
|
short= |
|
param= |
|
if [[ "${1:0:2}" = "--" && "$1" != "${1/=/}" ]];then |
|
param=1 |
|
set -- "${1%%=*}" "${1#*=}" "${@:2}" |
|
elif [[ "${1:0:2}" != "--" && "${1:0:1}" = "-" && 2 < "${#1}" ]]; then |
|
short=1 |
|
param=1 |
|
set -- "${1:0:2}" "${1:2}" "${@:2}" |
|
fi |
|
case "$1" in |
|
-h|--help) |
|
OPT_HELP="$1" |
|
;;& |
|
--debug) |
|
OPT_DEBUG="$1" |
|
;;& |
|
-k|--key) # KEYDEF |
|
keydefparse "$2" |
|
OPTS+=( "$1" "$KEYDEF" ) |
|
shift 2 |
|
;; |
|
$OPT01) # [PARAM] |
|
if [[ "$param" = 1 ]]; then |
|
OPTS+=( "$1=$2" ) |
|
shift 2 |
|
else |
|
OPTS+=( "$1" ) |
|
shift 1 |
|
fi |
|
;; |
|
$OPT1) # PARAM |
|
OPTS+=( "$1" "$2" ) |
|
shift 2 |
|
;; |
|
$OPT0) # |
|
OPTS+=( "$1" ) |
|
[[ -z "$short" && -n "$param" ]] && { |
|
echo "Warning: taking a unexpected parameter for $1: $2" |
|
set -- "${@:1:1}" "${@:3}" |
|
} |
|
[[ -n "$short" && -n "$param" ]] && set -- "${@:1:1}" "-${@:2:1}" "${@:3}" |
|
shift 1 |
|
;; |
|
--) |
|
ARGS=( "${@:2}" ) |
|
shift $# |
|
;; |
|
-*) |
|
echo "Error: unknown option: $1" |
|
exit 1 |
|
;; |
|
*) |
|
ARGS+=( "$1" ) |
|
shift 1 |
|
;; |
|
esac |
|
done |
|
[ "$extglob" = "1" ] && shopt -u extglob |
|
} |
|
|
|
function get_options () # <COMMAND> |
|
# Get options of COMMAND. |
|
# Returns: |
|
# OPT0 : Options for case statement which take no parameters. |
|
# OPT1 : Options for case statement which take a parameter. |
|
# OPT01 : Options for case statement which take a optional parameter. |
|
{ |
|
local tmp opt opts |
|
OPT0="" |
|
OPT1="" |
|
OPT01="" |
|
readarray -t opts < <("$1" --help|grep -E "^ *-"|sed -r -e 's/^ *//g' -e 's/^((-[^-])?((, +)?--[^ ,]+)*).*/\1/g') |
|
for opt in "${opts[@]}"; do |
|
tmp="${opt//\[=*/}" |
|
tmp="${tmp//=*/}" |
|
tmp="${tmp// /}" |
|
tmp="${tmp//,/|}" |
|
if [[ "$opt" = "${opt/=/}" ]]; then |
|
OPT0+="${OPT0:+|}$tmp" |
|
elif [[ "${opt}" =~ \[= ]]; then |
|
OPT01+="${OPT01:+|}$tmp" |
|
else |
|
OPT1+="${OPT1:+|}$tmp" |
|
fi |
|
done |
|
OPT0="@($OPT0)" |
|
OPT1="@($OPT1)" |
|
OPT01="@($OPT01)" |
|
} |
|
|
|
function main () # [OPTIONS ...] [FILES ...] |
|
# Sort wrapper for saving original order of lines which have the same key values. |
|
# Options: |
|
# See help or manpage of sort command as: |
|
# sort --help |
|
# man sort |
|
{ |
|
local OPT0 OPT1 |
|
local ARGS OPTS |
|
local TEMPLATES="" FIELDS="" KEYCOUNT=1 |
|
local filter |
|
get_options sort |
|
optparse "$@" |
|
if [ -n "$OPT_HELP" ]; then |
|
usage_default "$FUNCNAME" "${0##*/}" |
|
return |
|
fi |
|
if [ -n "$OPT_DEBUG" ]; then |
|
filter=( "cat" ) |
|
else |
|
filter=( sed -r -e 's/^([^,]*,){'"$KEYCOUNT"'}//g' ) |
|
fi |
|
awk '{printf("'"$TEMPLATES"'%s,%s\n", '"$FIELDS"' NR, $0)}' "${ARGS[@]}" \ |
|
| sort -t, "${OPTS[@]}" -k "${KEYCOUNT}n,${KEYCOUNT}n" \ |
|
| "${filter[@]}" |
|
} |
|
|
|
main "$@" |