diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index c89462cf..67bf5887 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -146,6 +146,7 @@ - util (`ble/util/buffer`): hide cursor in rendering `#D1758` e332dc5 - complete (`action:file`): always suffix `/` to complete symlinked directory names (reported by SuperSandro2000) `#D1759` 397ac1f - edit (command-help): show source files for functions `#D1779` 0000000 +- edit (`ble/builtin/exit`): defer exit in trap handlers (motivated by SuperSandro2000) `#D1782` 0000000 ## Fixes diff --git a/note.txt b/note.txt index 6faf6580..6fa09dfd 100644 --- a/note.txt +++ b/note.txt @@ -1806,6 +1806,22 @@ bash_tips ToDo ------------------------------------------------------------------------------- +2022-02-20 + + * trap (lastarg): 一応 heredoc 等を使えば eval の中から複数行の lastarg を設 + 定する事ができるのではないか。他に複数行で、eval されても余分な実行が起こら + ない様な方法はあるだろうか。不完全な引用符の場合には結局エラーが出力されて + しまう。 + + * exec: builtin sleep に対して C-c が効かない + + % 現在の実装だと bash-5.0 以下で C-c でループ中に走っている外部コマンドを止 + % めた時に応答がなくなってしまう。bash-5.1 以上では一旦実行が停止するものの + % 一応応答はする。 + + と思ったらこの問題は外部コマンドではなくて builtin sleep を呼び出している時 + 特有の問題だった。trap INT を設定しているのは問題なのだろうか。 + 2022-02-14 * 全ての ble.sh session にコマンドを送る機能があれば screen に再 attach した @@ -6104,6 +6120,66 @@ bash_tips Done (実装ログ) ------------------------------------------------------------------------------- +2022-02-20 + + * edit: work around exit called from EXIT trap (reported by SuperSandro2000) [#D1782] + https://github.com/akinomyoga/ble.sh/issues/179#issuecomment-1046122787 + + nix-shell の内部で exit を実行すると2回 [ble: exit] が表示される。これは + nix-shell が設定する EXIT trap の中で再度 exit が呼び出される為である。 + ble.sh は独自に trap を処理しているし、また blehook EXIT も実行したいので、 + 勝手に EXIT の中から本当に exit されると困る。なので、trap handler 実行中に + ble/builtin/exit が呼び出された時には DEBUG trap を設定して、trap の呼び出 + し元まで制御を戻して続きの処理を実行することにする。呼び出し元まで戻った時 + に exit が trap handler の中で呼び出されたという事を記録しておいて、trap + handler の処理の終わりに元々呼ばれた exit を呼び出し直す。 + + * exit を trap handler の中で実行した時にはそのまま終了するのではなくて、 + trap の処理が完了するまで待ってから終了するべきなのではないか → その様に + 実装した。既存の TRAPDEBUG の枠組みにコードを追加して実現した。 + + x fixed: exit 1 2 の様に余分な引数を指定した時、exit の準備をして [ble: + exit] まで表示した後に exit: too many arguments のエラーが発生して exit + がキャンセルされる。事前に引数の問題はチェックするべきである。 + + 取り敢えず実装して 3.0, 3.2, 5.0 で動作確認した。期待通りに動いている。以下 + のコマンドなどを使ってちゃんと全ての EXIT handler が実行される事を確認した。 + + $ trap 'exit 5' EXIT; exit + $ trap 'echo trap EXIT' EXIT; f1() { echo "$FUNCNAME $*"; exit "$1"; }; blehook EXIT+='f1 2 hello' EXIT+='f1 3 world';exit + + ? 関係ないが jobs がある時にユーザーが exit をキャンセルした時、後続の処理 + が実行されるのは問題ではないか? これに対しても何らかの対処が必要になるの + ではないか? + + ? もし現在の TRAPDEBUG の枠組みが信用できるのであれば try/catch の枠組みを + 整備しても良いのかもしれないという気がする。その方が見通しも良いのでは。 + 然し同じ commit でやるのは大げさな気がする。 + + 上記の exit キャンセルの際の振る舞いに関してはその枠組が整ってから対応す + るのが良い気がする。 + + →上の2つは別項目で処理する事にした。 + + x bash-5.2 以降で EXIT trap の stdout が /dev/null になっている。これはどう + したら良いのだろうか。うーん。EXIT trap の時だけ stdout/stdin を繋ぎ直す + のが良いだろうか。 + + 調べてみた所 bash-5.2 以降では exit も EXIT handler も現在の関数の内部で + 行われる様だ。これは bash-5.1 までの top-level で実行されていたときと振る + 舞いが異なる。と思ったが、bash-4.4..5.1 の振る舞いが変だっただけの様だ。 + + うーん。builtin exit を呼び出す時の redirection を削除するか? と思ったが、 + それだとやはり余分なエラーメッセージが出るのが心配である。という事を考え + ると、元々の stdout/stdin を何処かに記録しておくべきなのだろうという気が + する。 + + * edit: 現在の TRAPDEBUG の枠組みを拡張して try/catch/finally を実装できるのでは + Ref #D1783 + + もしその枠組がちゃんとできるのであれば edit.sh から util.sh に移動する。 + + 2022-02-19 * 2022-01-20 \C-x\C-v で bash-completion や terminal ID 等の情報も出力する [#D1781] @@ -6145,7 +6221,7 @@ bash_tips * done: starship * done: bash-it * done: sbp - * gitstatus + * git: gitstatus 取り敢えず思いつく物は全て登録したのでOK。 diff --git a/src/edit.sh b/src/edit.sh index aa90bb7b..69cf1b62 100644 --- a/src/edit.sh +++ b/src/edit.sh @@ -5620,6 +5620,7 @@ function ble-edit/exec/print-PS0 { fi } +_ble_builtin_exit_processing= function ble/builtin/exit/.read-arguments { [[ ! $_ble_attached || $_ble_edit_exec_inside_userspace ]] && ble/base/adjust-BASH_REMATCH @@ -5634,18 +5635,19 @@ function ble/builtin/exit/.read-arguments { opt_flags=${opt_flags}E fi done + if ((${#opt_args[@]}>=2)); then + ble/util/print "exit: too many arguments" >&2 + opt_flags=${opt_flags}E + fi [[ ! $_ble_attached || $_ble_edit_exec_inside_userspace ]] && ble/base/restore-BASH_REMATCH } function ble/builtin/exit { local ext=$? - if ble/util/is-running-in-subshell || [[ $_ble_decode_bind_state == none ]]; then - if (($#)); then - builtin exit "$@" - else - builtin exit "$ext" - fi - return 1 + if [[ ! $_ble_builtin_trap_processing ]] && { ble/util/is-running-in-subshell || [[ $_ble_decode_bind_state == none ]]; }; then + (($#)) || set -- "$ext" + builtin exit "$@" + return $? # オプションの指定間違いなどで失敗する可能性がある。 fi local set shopt; ble/base/.adjust-bash-options set shopt @@ -5659,30 +5661,45 @@ function ble/builtin/exit { fi ((${#opt_args[@]})) || ble/array#push opt_args "$ext" - local joblist - ble/util/joblist - if ((${#joblist[@]})); then - local ret - while - local cancel_reason= - if ble/util/assign ret 'compgen -A stopped -- ""' 2>/dev/null; [[ $ret ]]; then - cancel_reason='stopped jobs' - elif [[ :$opts: == *:checkjobs:* ]]; then - if ble/util/assign ret 'compgen -A running -- ""' 2>/dev/null; [[ $ret ]]; then - cancel_reason='running jobs' + if [[ $_ble_builtin_trap_processing ]]; then + # Note #D1782: trap の中で処理している時は exit は trap の側で処理する。な + # ので exit は延期して一旦元の呼び出し元まで戻る。これによって細かな動作の + # 違いが問題になる可能性はある。例えば trap の中で time で時間計測中だった + # 場合、時間計測が中止されず結果が出力される。 + _ble_edit_exec_TRAPDEBUG_EXIT=$opt_args + ble-edit/exec:gexec/.TRAPDEBUG/trap + return 0 + fi + + if [[ ! $_ble_builtin_exit_processing ]]; then + # 終了確認と [ble: exit] の出力 + + local joblist + ble/util/joblist + if ((${#joblist[@]})); then + local ret + while + local cancel_reason= + if ble/util/assign ret 'compgen -A stopped -- ""' 2>/dev/null; [[ $ret ]]; then + cancel_reason='stopped jobs' + elif [[ :$opts: == *:checkjobs:* ]]; then + if ble/util/assign ret 'compgen -A running -- ""' 2>/dev/null; [[ $ret ]]; then + cancel_reason='running jobs' + fi fi - fi - [[ $cancel_reason ]] - do - jobs - ble/builtin/read -ep "\e[38;5;12m[ble: There are $cancel_reason]\e[m Leave the shell anyway? [yes/No] " ret - case $ret in - ([yY]|[yY][eE][sS]) break ;; - ([nN]|[nN][oO]|'') - ble/base/.restore-bash-options set shopt - return 0 ;; - esac - done + [[ $cancel_reason ]] + do + jobs + ble/builtin/read -ep "\e[38;5;12m[ble: There are $cancel_reason]\e[m Leave the shell anyway? [yes/No] " ret + case $ret in + ([yY]|[yY][eE][sS]) break ;; + ([nN]|[nN][oO]|'') + ble/base/.restore-bash-options set shopt + return 0 ;; + esac + done + fi + ble/util/print "${_ble_term_setaf[12]}[ble: exit]$_ble_term_sgr0" >&2 fi # Note #D1765: Bash 4.4..5.1 では "{ time { exit 2>/dev/tty; } } 2>/dev/null" @@ -5710,12 +5727,17 @@ function ble/builtin/exit { TIMEFORMAT= fi - ble/util/print "${_ble_term_setaf[12]}[ble: exit]$_ble_term_sgr0" >&2 ble/base/.restore-bash-options set shopt + _ble_builtin_exit_processing=1 + ble/fd#alloc _ble_builtin_exit_stdout '>&1' # EXIT trap で stdin/stdout を復元する + ble/fd#alloc _ble_builtin_exit_stderr '>&2' builtin exit "${opt_args[@]}" &>/dev/null builtin exit "${opt_args[@]}" &>/dev/null # exit に失敗した時はできるだけ元の状態に戻す + _ble_builtin_exit_processing= + ble/fd#close _ble_builtin_exit_stdout + ble/fd#close _ble_builtin_exit_stderr if ((40400<=_ble_bash&&_ble_bash<50200)); then builtin eval -- "$global_TIMEFORMAT" ble/variable#copy-state local_TIMEFORMAT TIMEFORMAT @@ -5958,6 +5980,8 @@ _ble_edit_exec_TRAPDEBUG_enabled= _ble_edit_exec_TRAPDEBUG_lastexit= _ble_edit_exec_TRAPDEBUG_lastarg= _ble_edit_exec_TRAPDEBUG_postproc= +_ble_edit_exec_TRAPDEBUG_INT= +_ble_edit_exec_TRAPDEBUG_EXIT= _ble_edit_exec_inside_begin= _ble_edit_exec_inside_prologue= _ble_edit_exec_inside_userspace= @@ -5982,7 +6006,7 @@ function ble-edit/exec:gexec/.TRAPDEBUG/trap { builtin trap -- 'ble-edit/exec:gexec/.TRAPDEBUG "$*"; builtin eval -- "$_ble_edit_exec_TRAPDEBUG_postproc"' DEBUG # Note: 以下は条件付きで user trap を直接 trap するコード。 - # if [[ $_ble_attached && _ble_edit_exec_INT -ne 0 || :$1: == *:filter:* ]]; then + # if [[ $_ble_attached && _ble_edit_exec_TRAPDEBUG_INT || :$1: == *:filter:* ]]; then # builtin trap -- 'ble-edit/exec:gexec/.TRAPDEBUG "$*"; builtin eval -- "$_ble_edit_exec_TRAPDEBUG_postproc"' DEBUG # else # local user_trap=${_ble_builtin_trap_handlers[_ble_builtin_trap_DEBUG]} @@ -6014,17 +6038,56 @@ function ble-edit/exec:gexec/.TRAPDEBUG { [[ ! ( ${FUNCNAME[1]-} == _ble_prompt_update__eval_prompt_command_1 && ( $bash_command == 'ble-edit/exec/.setexit '* || $bash_command == 'BASH_COMMAND='*' builtin eval -- '* ) ) ]] || return 0 [[ ! ${_ble_builtin_trap_inside-} ]] || return 0 - if [[ $_ble_attached ]] && ((_ble_edit_exec_INT!=0)); then - local __set __shopt; ble/base/.adjust-bash-options __set __shopt + # Handle EXIT + if [[ $_ble_edit_exec_TRAPDEBUG_EXIT ]]; then + # 或る特定のレベルまでは素通りする (そもそも exit なのでユーザーの DEBUG + # trap も処理しなくて良い)。 + local flag_clear= flag_exit= + if [[ ! $_ble_builtin_trap_processing ]] || ((${#FUNCNAME[*]}<=1)); then + # 本来は此処に来る事はない筈 + flag_clear=1 + flag_exit=$_ble_edit_exec_TRAPDEBUG_EXIT + else + case "${FUNCNAME[1]-} ${FUNCNAME[2]-}" in + ('ble/builtin/trap/invoke.sandbox ble/builtin/trap/invoke') + _ble_trap_done=exit + _ble_trap_lastarg=$_ble_edit_exec_TRAPDEBUG_EXIT + _ble_edit_exec_TRAPDEBUG_postproc='return 0' + flag_clear=1 ;; + ('blehook/invoke ble/builtin/trap/.handler') + _ble_builtin_trap_processing=exit:$_ble_edit_exec_TRAPDEBUG_EXIT + flag_clear=1 ;; + ('ble/builtin/trap/invoke '* | 'ble/builtin/trap/.handler '* | 'ble-edit/exec:gexec/.TRAPDEBUG '*) + # 此処にも本来は来ない筈 + flag_clear=1 ;; + (*) + # trap handler 内部の処理は全てスキップして呼び出し元に戻る。 + _ble_edit_exec_TRAPDEBUG_postproc="{ return 130 || break; } &>/dev/null" ;; + esac + fi + + if [[ $flag_clear ]]; then + if [[ ! ${_ble_builtin_trap_handlers[_ble_builtin_trap_DEBUG]+set} ]]; then + _ble_edit_exec_TRAPDEBUG_postproc="builtin trap - DEBUG${_ble_edit_exec_TRAPDEBUG_postproc:+;$_ble_edit_exec_TRAPDEBUG_postproc}" + fi + _ble_edit_exec_TRAPDEBUG_EXIT= + if [[ $flag_exit ]]; then + builtin exit "$flag_exit" + fi + fi + + elif [[ $_ble_edit_exec_TRAPDEBUG_INT ]]; then + local IFS=$_ble_term_IFS __set __shopt + ble/base/.adjust-bash-options __set __shopt # Run user DEBUG trap in the sandbox + local _ble_builtin_trap_processing=$_ble_builtin_trap_DEBUG local _ble_builtin_trap_postproc ble/util/setexit "$__lastexit" "$__lastarg" ble/builtin/trap/invoke DEBUG _ble_edit_exec_TRAPDEBUG_postproc=$_ble_builtin_trap_postproc # unused # Handle INT - local IFS=$_ble_term_IFS local depth=${#FUNCNAME[*]} if ((depth>=2)) && ! ble/string#match "${FUNCNAME[*]:1}" '^ble-edit/exec:gexec/\.|(^| )ble/builtin/trap/\.handler'; then # 関数内にいるが、ble-edit/exec:gexec/. の中ではない時 @@ -6033,13 +6096,13 @@ function ble-edit/exec:gexec/.TRAPDEBUG { local lineno=${_ble_term_setaf[2]}${BASH_LINENO[0]} local func=${_ble_term_setaf[6]}' ('${_ble_term_setaf[4]}${FUNCNAME[1]}${1:+ $1}${_ble_term_setaf[6]}')' ble/util/print "${_ble_term_setaf[9]}[SIGINT]$_ble_term_sgr0 $source$sep$lineno$func$_ble_term_sgr0" >/dev/tty - _ble_edit_exec_TRAPDEBUG_postproc="{ return $_ble_edit_exec_INT || break; } &>/dev/null" + _ble_edit_exec_TRAPDEBUG_postproc="{ return $_ble_edit_exec_TRAPDEBUG_INT || break; } &>/dev/null" elif ((depth==1)) && ! ble/string#match "$bash_command" '^ble-edit/exec:gexec/\.'; then # 一番外側で、ble-edit/exec:gexec/. 関数ではない時 local source=${_ble_term_setaf[5]}global local sep=${_ble_term_setaf[6]}: ble/util/print "${_ble_term_setaf[9]}[SIGINT]$_ble_term_sgr0 $source$sep$_ble_term_sgr0 $bash_command" >/dev/tty - _ble_edit_exec_TRAPDEBUG_postproc="{ return $_ble_edit_exec_INT || break; } &>/dev/null" + _ble_edit_exec_TRAPDEBUG_postproc="break &>/dev/null" fi ble/base/.restore-bash-options __set __shopt @@ -6143,7 +6206,7 @@ function ble-edit/exec:gexec/.TRAPINT { ((_ble_bash>=40300)) || sig=128 # bash-4.2 以下は 128 if [[ $_ble_attached ]]; then ble/util/print "$_ble_term_bold^C$_ble_term_sgr0" >&2 - _ble_edit_exec_INT=$sig + _ble_edit_exec_TRAPDEBUG_INT=$sig ble-edit/exec:gexec/.TRAPDEBUG/trap else _ble_builtin_trap_postproc="{ return $sig || break; } &>/dev/tty" @@ -6243,14 +6306,13 @@ function ble-edit/exec:gexec/.end { function ble-edit/exec:gexec/.prologue { _ble_edit_exec_inside_prologue=1 local IFS=$_ble_term_IFS - BASH_COMMAND=$1 _ble_edit_exec_BASH_COMMAND=$1 ble-edit/restore-PS1 ble-edit/restore-READLINE ble-edit/restore-IGNOREEOF builtin unset -v HISTCMD; ble/history/get-count -v HISTCMD - _ble_edit_exec_INT=0 + _ble_edit_exec_TRAPDEBUG_INT= ble/util/joblist.clear ble-edit/exec:gexec/invoke-hook-with-setexit PREEXEC "$_ble_edit_exec_BASH_COMMAND" ble-edit/exec/print-PS0 >&$_ble_util_fd_stdout 2>&$_ble_util_fd_stderr @@ -6302,10 +6364,12 @@ function ble-edit/exec:gexec/.epilogue { # Note: 他の関数呼び出しよりも先 builtin eval -- "$_ble_bash_FUNCNEST_adjust" ble/base/adjust-builtin-wrappers-1 - if ((_ble_edit_exec_lastexit==0)); then - _ble_edit_exec_lastexit=$_ble_edit_exec_INT + if [[ $_ble_edit_exec_TRAPDEBUG_INT ]]; then + if ((_ble_edit_exec_lastexit==0)); then + _ble_edit_exec_lastexit=$_ble_edit_exec_TRAPDEBUG_INT + fi + _ble_edit_exec_TRAPDEBUG_INT= fi - _ble_edit_exec_INT=0 local IFS=$_ble_term_IFS # Note: builtin trap -- - DEBUG は此処では何故か効かない diff --git a/src/util.sh b/src/util.sh index ee74cb5f..efbdc529 100644 --- a/src/util.sh +++ b/src/util.sh @@ -1936,7 +1936,8 @@ _ble_builtin_trap_signames=() _ble_builtin_trap_reserved=() _ble_builtin_trap_handlers=() _ble_builtin_trap_DEBUG= -_ble_builtin_trap_inside= +_ble_builtin_trap_inside= # ble/builtin/trap 処理中かどうか +_ble_builtin_trap_processing= # ble/buitlin/trap/.handler 実行中かどうか builtin eval -- "${_ble_util_gdict_declare//NAME/_ble_builtin_trap_n2i}" function ble/builtin/trap/.register { local index=$1 name=$2 @@ -2078,6 +2079,7 @@ _ble_builtin_trap_user_lastexit= ## @fn ble/builtin/trap/invoke.sandbox ## @var[in] _ble_trap_handler ## @var[out] _ble_trap_done +## @var[in,out] _ble_trap_lastexit _ble_trap_lastarg function ble/builtin/trap/invoke.sandbox { local _ble_trap_count for ((_ble_trap_count=0;_ble_trap_count<1;_ble_trap_count++)); do @@ -2148,6 +2150,17 @@ function ble/builtin/trap/invoke { # の状態を keep するぐらいしかない気がする。 _ble_builtin_trap_lastarg=$ext _ble_builtin_trap_postproc="return $ext" ;; + (exit) + # Note #D1782: trap handler の中で ble/builtin/exit (edit.sh) を呼 + # び出した時は、即座に bash を終了せずに取り敢えずは trap の処理 + # は完了させる。TRAPDEBUGによって _ble_trap_done=exit が設定され + # る。また、元々 exit に渡された引数は $_ble_trap_lastarg に設定 + # される。 + # Note #D1782: 他の trap の中で更にまた DEBUG trap が起動している + # 時などの為に、builtin exit ではなく ble/builtin/exit を再度呼 + # び出し直す。 + _ble_builtin_trap_lastarg=$_ble_trap_lastarg + _ble_builtin_trap_postproc="ble/builtin/exit $_ble_trap_lastarg" ;; esac # save $_ and $? for user trap handlers @@ -2167,11 +2180,27 @@ function ble/builtin/trap/.handler { local FUNCNEST= IFS=$_ble_term_IFS local set shopt; ble/base/.adjust-bash-options set shopt + local _ble_builtin_trap_processing=$_ble_trap_sig + # 透過 _ble_builtin_trap_postproc を設定 local _ble_local_q=\' _ble_local_Q="'\''" _ble_builtin_trap_lastarg=$_ble_trap_lastarg _ble_builtin_trap_postproc="ble/util/setexit $_ble_trap_lastexit" + # Note #D1782: ble/builtin/exit で "builtin exit ... &>/dev/null" と + # したリダイレクションを元に戻す。元々 builtin exit が出力するエラー + # を無視する為のリダイレクトだが、続いて呼び出される EXIT trap に + # 対してもこのリダイレクションが有効なままになる (但し、 + # bash-4.4..5.1 ではバグで top-level まで制御を戻してから EXIT + # trap 他の処理が実行されるので、EXIT trap は tty に繋がった状態で + # 実行される)。他の trap が予期せず呼び出された場合にも同様の事が + # 起こる。trap handler を exit を実行した文脈での stdout/stderr で + # 実行する為に、stdout/stderr を保存していた物に繋ぎ戻す。 + if [[ $_ble_builtin_exit_processing ]]; then + exec 1>&- 1>&"$_ble_builtin_exit_stdout" + exec 2>&- 2>&"$_ble_builtin_exit_stderr" + fi + # ble.sh hook ble/util/joblist.check ble/util/setexit "$_ble_trap_lastexit" "$_ble_trap_lastarg" @@ -2182,6 +2211,11 @@ function ble/builtin/trap/.handler { ble/util/setexit "$_ble_trap_lastexit" "$_ble_trap_lastarg" ble/builtin/trap/invoke "$_ble_trap_sig" + # 何処かの時点で exit が要求された場合 + if [[ $_ble_builtin_trap_processing == exit:* && $_ble_builtin_trap_postproc != 'ble/builtin/exit '* ]]; then + _ble_builtin_trap_postproc="ble/builtin/exit ${_ble_builtin_trap_processing#exit:}" + fi + # Note #D1757: 現在 eval が終わった後の $_ を設定する為には eval に # '#' "$lastarg" を余分に渡すしかないので改行を含める事はできない。 # 中途半端な値を設定するよりは最初から何も設定しない事にする。ここ設