diff --git a/shell/completion.bash b/shell/completion.bash index 85e269aa..b62cf004 100644 --- a/shell/completion.bash +++ b/shell/completion.bash @@ -39,6 +39,32 @@ __fzf_defaults() { echo "${FZF_DEFAULT_OPTS-} $2" } +# This function performs `exec awk "$@"` safely by working around awk +# compatibility issues. +# +# Note: To reduce an extra fork, this function performs "exec" so is expected +# to be run as the last command in a subshell. +# +# Note: This function is included with {completion,key-bindings}.{bash,zsh} and +# synchronized. +__fzf_exec_awk() { + if [[ -z ${__fzf_awk-} ]]; then + __fzf_awk=awk + + # choose the faster mawk if: it's installed && build date >= 20230322 && + # version >= 1.3.4 + local n x y z d + IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null) + [[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk + fi + + # Note: macOS awk has a quirk that it stops processing at all when it sees + # any data not following UTF-8 in the input stream when the current LC_CTYPE + # specifies the UTF-8 encoding. To work around this quirk, one needs to + # specify LC_ALL=C to change the current encoding to the plain one. + LC_ALL=C exec "$__fzf_awk" "$@" +} + __fzf_comprun() { if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then _fzf_comprun "$@" @@ -364,7 +390,7 @@ _fzf_complete() { fi local cur selected trigger cmd post - post="$(caller 0 | command awk '{print $2}')_post" + post="$(caller 0 | __fzf_exec_awk '{print $2}')_post" type -t "$post" > /dev/null 2>&1 || post='command cat' trigger=${FZF_COMPLETION_TRIGGER-'**'} @@ -443,7 +469,7 @@ _fzf_proc_completion() { } _fzf_proc_completion_post() { - command awk '{print $2}' + __fzf_exec_awk '{print $2}' } # To use custom hostname lists, override __fzf_list_hosts. @@ -475,7 +501,7 @@ if ! declare -F __fzf_list_hosts > /dev/null; then shopt -u dotglob nocaseglob failglob shopt -s nullglob - command awk ' + __fzf_exec_awk ' tolower($1) ~ /^host(name)?$/ { for (i = 2; i <= NF; i++) if ($i !~ /[*?%]/) @@ -484,7 +510,7 @@ if ! declare -F __fzf_list_hosts > /dev/null; then ' ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null ) \ <( - command awk -F ',' ' + __fzf_exec_awk -F ',' ' match($0, /^[[a-z0-9.,:-]+/) { $0 = substr($0, 1, RLENGTH) gsub(/\[/, "") @@ -494,7 +520,7 @@ if ! declare -F __fzf_list_hosts > /dev/null; then ' ~/.ssh/known_hosts 2> /dev/null ) \ <( - command awk ' + __fzf_exec_awk ' /^[[:blank:]]*(#|$)|0\.0\.0\.0/ { next } { sub(/#.*/, "") @@ -523,7 +549,7 @@ _fzf_complete_ssh() { *) local user= [[ "$2" =~ '@' ]] && user="${2%%@*}@" - _fzf_complete +m -- "$@" < <(__fzf_list_hosts | command awk -v user="$user" '{print user $0}') + _fzf_complete +m -- "$@" < <(__fzf_list_hosts | __fzf_exec_awk -v user="$user" '{print user $0}') ;; esac } diff --git a/shell/completion.zsh b/shell/completion.zsh index 3fbdbb20..d59796bc 100644 --- a/shell/completion.zsh +++ b/shell/completion.zsh @@ -104,6 +104,32 @@ __fzf_defaults() { echo -E "${FZF_DEFAULT_OPTS-} $2" } +# This function performs `exec awk "$@"` safely by working around awk +# compatibility issues. +# +# Note: To reduce an extra fork, this function performs "exec" so is expected +# to be run as the last command in a subshell. +# +# Note: This function is included with {completion,key-bindings}.{bash,zsh} and +# synchronized. +__fzf_exec_awk() { + if [[ -z ${__fzf_awk-} ]]; then + __fzf_awk=awk + + # choose the faster mawk if: it's installed && build date >= 20230322 && + # version >= 1.3.4 + local n x y z d + IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null) + [[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk + fi + + # Note: macOS awk has a quirk that it stops processing at all when it sees + # any data not following UTF-8 in the input stream when the current LC_CTYPE + # specifies the UTF-8 encoding. To work around this quirk, one needs to + # specify LC_ALL=C to change the current encoding to the plain one. + LC_ALL=C exec "$__fzf_awk" "$@" +} + __fzf_comprun() { if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then _fzf_comprun "$@" @@ -253,7 +279,7 @@ if ! declare -f __fzf_list_hosts > /dev/null; then # when no matching is found. setopt GLOB NO_DOT_GLOB CASE_GLOB NO_NOMATCH NULL_GLOB - command awk ' + __fzf_exec_awk ' tolower($1) ~ /^host(name)?$/ { for (i = 2; i <= NF; i++) if ($i !~ /[*?%]/) @@ -262,7 +288,7 @@ if ! declare -f __fzf_list_hosts > /dev/null; then ' ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null ) \ <( - command awk -F ',' ' + __fzf_exec_awk -F ',' ' match($0, /^[[a-z0-9.,:-]+/) { $0 = substr($0, 1, RLENGTH) gsub(/\[/, "") @@ -272,7 +298,7 @@ if ! declare -f __fzf_list_hosts > /dev/null; then ' ~/.ssh/known_hosts 2> /dev/null ) \ <( - command awk ' + __fzf_exec_awk ' /^[[:blank:]]*(#|$)|0\.0\.0\.0/ { next } { sub(/#.*/, "") @@ -300,7 +326,7 @@ _fzf_complete_ssh() { *) local user [[ $prefix =~ @ ]] && user="${prefix%%@*}@" - _fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}') + _fzf_complete +m -- "$@" < <(__fzf_list_hosts | __fzf_exec_awk -v user="$user" '{print user $0}') ;; esac } @@ -358,7 +384,7 @@ _fzf_complete_kill() { } _fzf_complete_kill_post() { - awk '{print $2}' + __fzf_exec_awk '{print $2}' } fzf-completion() { diff --git a/shell/key-bindings.bash b/shell/key-bindings.bash index 8b0ef76c..946bc2d8 100644 --- a/shell/key-bindings.bash +++ b/shell/key-bindings.bash @@ -25,6 +25,32 @@ __fzf_defaults() { echo "${FZF_DEFAULT_OPTS-} $2" } +# This function performs `exec awk "$@"` safely by working around awk +# compatibility issues. +# +# Note: To reduce an extra fork, this function performs "exec" so is expected +# to be run as the last command in a subshell. +# +# Note: This function is included with {completion,key-bindings}.{bash,zsh} and +# synchronized. +__fzf_exec_awk() { + if [[ -z ${__fzf_awk-} ]]; then + __fzf_awk=awk + + # choose the faster mawk if: it's installed && build date >= 20230322 && + # version >= 1.3.4 + local n x y z d + IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null) + [[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk + fi + + # Note: macOS awk has a quirk that it stops processing at all when it sees + # any data not following UTF-8 in the input stream when the current LC_CTYPE + # specifies the UTF-8 encoding. To work around this quirk, one needs to + # specify LC_ALL=C to change the current encoding to the plain one. + LC_ALL=C exec "$__fzf_awk" "$@" +} + __fzf_select__() { FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \ FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \ @@ -74,13 +100,7 @@ if command -v perl > /dev/null; then } else # awk - fallback for POSIX systems __fzf_history__() { - local output script n x y z d - if [[ -z $__fzf_awk ]]; then - __fzf_awk=awk - # choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4 - IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null) - [[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk - fi + local output script [[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } } NR==1 { b = substr($0, 2); next } @@ -90,7 +110,7 @@ else # awk - fallback for POSIX systems output=$( set +o pipefail builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '$'\n' )* ; ::= [^\n]* ( $'\n' )* - command $__fzf_awk "$script" | # ( $'\t'$'\000' )* + __fzf_exec_awk "$script" | # ( $'\t'$'\000' )* FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \ FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE" ) || return diff --git a/shell/key-bindings.zsh b/shell/key-bindings.zsh index 9c4215e0..51defeab 100644 --- a/shell/key-bindings.zsh +++ b/shell/key-bindings.zsh @@ -46,6 +46,32 @@ __fzf_defaults() { echo -E "${FZF_DEFAULT_OPTS-} $2" } +# This function performs `exec awk "$@"` safely by working around awk +# compatibility issues. +# +# Note: To reduce an extra fork, this function performs "exec" so is expected +# to be run as the last command in a subshell. +# +# Note: This function is included with {completion,key-bindings}.{bash,zsh} and +# synchronized. +__fzf_exec_awk() { + if [[ -z ${__fzf_awk-} ]]; then + __fzf_awk=awk + + # choose the faster mawk if: it's installed && build date >= 20230322 && + # version >= 1.3.4 + local n x y z d + IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null) + [[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk + fi + + # Note: macOS awk has a quirk that it stops processing at all when it sees + # any data not following UTF-8 in the input stream when the current LC_CTYPE + # specifies the UTF-8 encoding. To work around this quirk, one needs to + # specify LC_ALL=C to change the current encoding to the plain one. + LC_ALL=C exec "$__fzf_awk" "$@" +} + # CTRL-T - Paste the selected file path(s) into the command line __fzf_select() { setopt localoptions pipefail no_aliases 2> /dev/null @@ -117,13 +143,13 @@ fzf-history-widget() { FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \ FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))" else - selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' | + selected="$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' | FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \ FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))" fi local ret=$? if [ -n "$selected" ]; then - if [[ $(awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then + if [[ $(__fzf_exec_awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then zle vi-fetch-history -n $MATCH else # selected is a custom query, not from history LBUFFER="$selected"