From c7cbd7561bd287bc2f86a985b22141093c1cccdd Mon Sep 17 00:00:00 2001 From: WATANABE Yuki Date: Thu, 18 Apr 2024 00:11:27 +0900 Subject: [PATCH 1/2] Refactor input_interactive --- input.c | 56 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/input.c b/input.c index 888c0c5b..25ef3544 100644 --- a/input.c +++ b/input.c @@ -1,6 +1,6 @@ /* Yash: yet another shell */ /* input.c: functions for input of command line */ -/* (C) 2007-2019 magicant */ +/* (C) 2007-2024 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -238,14 +238,13 @@ inputresult_T optimized_read_input( inputresult_T input_interactive(struct xwcsbuf_T *buf, void *inputinfo) { struct input_interactive_info_T *info = inputinfo; - struct promptset_T prompt; - if (info->prompttype == 1) { if (!posixly_correct) exec_variable_as_auxiliary_(VAR_PROMPT_COMMAND); check_mail(); } - prompt = get_prompt(info->prompttype); + + struct promptset_T prompt = get_prompt(info->prompttype); if (do_job_control) print_job_status_all(); /* Note: no commands must be executed between `print_job_status_all' here @@ -253,25 +252,22 @@ inputresult_T input_interactive(struct xwcsbuf_T *buf, void *inputinfo) * `handle_sigchld' must not be called from any other function until it is * called from `wait_for_input' during the line-editing. */ + wchar_t *line; + inputresult_T result; + #if YASH_ENABLE_LINEEDIT /* read a line using line editing */ if (info->fileinfo->fd == STDIN_FILENO && shopt_lineedit != SHOPT_NOLINEEDIT) { - wchar_t *line; - inputresult_T result; - result = le_readline(prompt, true, &line); - if (result != INPUT_ERROR) { - free_prompt(prompt); - if (result == INPUT_OK) { - if (info->prompttype == 1) - info->prompttype = 2; -#if YASH_ENABLE_HISTORY - add_history(line); -#endif - wb_catfree(buf, line); - } - return result; + switch (result) { + case INPUT_OK: + goto success; + case INPUT_EOF: + case INPUT_INTERRUPTED: + goto done; + case INPUT_ERROR: + break; // Line-editing unavailable. Fall back to plain input. } } #endif /* YASH_ENABLE_LINEEDIT */ @@ -279,21 +275,27 @@ inputresult_T input_interactive(struct xwcsbuf_T *buf, void *inputinfo) /* read a line without line editing */ print_prompt(prompt.main); print_prompt(prompt.styler); - if (info->prompttype == 1) - info->prompttype = 2; - int result; -#if YASH_ENABLE_HISTORY - size_t oldlen = buf->length; -#endif - result = input_file(buf, info->fileinfo); + xwcsbuf_T linebuf; + wb_init(&linebuf); + result = input_file(&linebuf, info->fileinfo); + line = wb_towcs(&linebuf); print_prompt(PROMPT_RESET); - free_prompt(prompt); +#if YASH_ENABLE_LINEEDIT +success: +#endif + if (info->prompttype == 1) + info->prompttype = 2; #if YASH_ENABLE_HISTORY - add_history(buf->contents + oldlen); + add_history(line); #endif + wb_catfree(buf, line); +#if YASH_ENABLE_LINEEDIT +done: +#endif + free_prompt(prompt); return result; } From b71b64135456c83c4909ce3e41bae26b460b9aca Mon Sep 17 00:00:00 2001 From: WATANABE Yuki Date: Thu, 18 Apr 2024 00:31:09 +0900 Subject: [PATCH 2/2] Post-prompt command --- NEWS | 2 ++ NEWS.ja | 2 ++ doc/_set.txt | 1 + doc/interact.txt | 13 ++++++++++--- doc/ja/_set.txt | 1 + doc/ja/interact.txt | 5 ++++- doc/ja/params.txt | 9 ++++++++- doc/ja/posix.txt | 2 +- doc/params.txt | 13 ++++++++++++- doc/posix.txt | 5 +++-- input.c | 37 +++++++++++++++++++++++++++++++++++++ share/initialization/common | 1 + tests/prompt-y.tst | 36 ++++++++++++++++++++++++++++++++++++ tests/run-test.sh | 6 +++--- variable.h | 4 +++- 15 files changed, 124 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index 725c0773..e57aa474 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ History of Yash ## Yash 2.57 (Unreleased) + - Added support for the "$POST_PROMPT_COMMAND" variable, whose value + is executed after reading a command line in the interactive shell. - [line-editing] Fixed the spurious error message printed when completing after `git config alias.` with the nounset shell option enabled. diff --git a/NEWS.ja b/NEWS.ja index 905dcd3b..84a8ac08 100644 --- a/NEWS.ja +++ b/NEWS.ja @@ -2,6 +2,8 @@ Yash 更新履歴 ## Yash 2.57 (未リリース) + - "$POST_PROMPT_COMMAND" に対応。対話モードでコマンドを読むごとに + 変数の値が実行される - [行編集] nounset オプション有効時に `git config alias.` に続けて 補完をしようとするとエラーが出るのを修正 - [行編集] カーソルがバックスラッシュの直後にある時に補完をすると diff --git a/doc/_set.txt b/doc/_set.txt index b0fbfbc9..7e62e76a 100644 --- a/doc/_set.txt +++ b/doc/_set.txt @@ -202,6 +202,7 @@ This option enables the link:posix.html[POSIXly-correct mode]. When this option is disabled, the <> is temporarily disabled while the shell is executing commands defined in the link:params.html#sv-command_not_found_handler[+COMMAND_NOT_FOUND_HANDLER+], +link:params.html#sv-post_prompt_command[+POST_PROMPT_COMMAND+], link:params.html#sv-prompt_command[+PROMPT_COMMAND+], or link:params.html#sv-yash_after_cd[+YASH_AFTER_CD+] variable. diff --git a/doc/interact.txt b/doc/interact.txt index 7d8fb0d1..25c73cdb 100644 --- a/doc/interact.txt +++ b/doc/interact.txt @@ -178,9 +178,16 @@ variables can be defined with a name prefixed with +YASH_+ (e.g. link:params.html#sv-yash_ps1[+YASH_PS1+]). This allows using a different prompt string than that in the POSIXly-correct mode. -When the shell is not in the link:posix.html[POSIXly-correct mode], -the value of the link:params.html#sv-prompt_command[+PROMPT_COMMAND+ variable] -is executed before each prompt. +When the shell is not in the link:posix.html[POSIXly-correct mode]: + +- The value of the link:params.html#sv-prompt_command[+PROMPT_COMMAND+ variable] + is executed before each prompt. +- The value of the + link:params.html#sv-post_prompt_command[+POST_PROMPT_COMMAND+ variable] is + executed after each line is input. While the execution, the + link:params.html#sv-command[+COMMAND+ variable] is set to the just input + line. You can even modify the variable to manipulate the command to be + executed. If you unset the variable, the command will not be executed. [[history]] == Command history diff --git a/doc/ja/_set.txt b/doc/ja/_set.txt index b27fb7e5..0cb37093 100644 --- a/doc/ja/_set.txt +++ b/doc/ja/_set.txt @@ -134,6 +134,7 @@ link:syntax.html#for[For ループ]が{zwsp}link:exec.html#function[関数]の [[so-traceall]]trace-all:: このオプションは、補助コマンド実行中も <>を機能させるかどうかを指定します。補助コマンドとは、 link:params.html#sv-command_not_found_handler[+COMMAND_NOT_FOUND_HANDLER+]、 +link:params.html#sv-post_prompt_command[+POST_PROMPT_COMMAND+]、 link:params.html#sv-prompt_command[+PROMPT_COMMAND+]、および link:params.html#sv-yash_after_cd[+YASH_AFTER_CD+] 変数の値として定義され、特定のタイミングで解釈・実行されるコマンドです。 diff --git a/doc/ja/interact.txt b/doc/ja/interact.txt index 517a2569..3de6c7e7 100644 --- a/doc/ja/interact.txt +++ b/doc/ja/interact.txt @@ -98,7 +98,10 @@ link:lineedit.html#prediction[コマンドライン推定]を使用している link:posix.html[POSIX 準拠モード]でないときは、上記の変数は名前に +YASH_+ を付けた名前 (例えば link:params.html#sv-yash_ps1[+YASH_PS1+]) で定義することもできます。これにより、POSIX 準拠モードとは異なるプロンプトを使い分けることができます。 -link:posix.html[POSIX 準拠モード]でないときは、プロンプトを出す前に link:params.html#sv-prompt_command[+PROMPT_COMMAND+ 変数]の値がコマンドとして実行されます。 +link:posix.html[POSIX 準拠モード]でないときは、以下の変数が評価されます。 + +- プロンプトを出す前に link:params.html#sv-prompt_command[+PROMPT_COMMAND+ 変数]の値がコマンドとして実行されます。 +- コマンドが一行分入力されるたびに link:params.html#sv-post_prompt_command[+POST_PROMPT_COMMAND+ 変数]の値がコマンドとして実行されます。実行中は入力されたコマンドが link:params.html#sv-command[+COMMAND+ 変数]に代入されます。この変数の値を変更することで実行されるコマンドを改変することもできます。変数を削除するとコマンドは実行されません。 [[history]] == コマンド履歴 diff --git a/doc/ja/params.txt b/doc/ja/params.txt index a7afea58..55fbbb0a 100644 --- a/doc/ja/params.txt +++ b/doc/ja/params.txt @@ -82,6 +82,10 @@ dfn:[変数]とはユーザが自由に代入可能なパラメータです。 [[sv-columns]]+COLUMNS+:: この変数は端末ウィンドウの横幅 (文字数) を指定します。この変数が設定されている場合、デフォルトの横幅ではなくこの変数の値で指定された横幅が{zwsp}link:lineedit.html[行編集]で使われます。 +[[sv-command]]+COMMAND+:: +<> 変数が実行される間、この変数は直前に入力されたコマンドを示します。 ++POST_PROMPT_COMMAND+ の実行が終わるとこの変数は消去されます。 + [[sv-command_not_found_handler]]+COMMAND_NOT_FOUND_HANDLER+:: シェルが実行しようとしたコマンドが見つからなかったとき、この変数の値がコマンドとして実行されます。不明なコマンドを実行したときに何か別のコマンドを実行させたい時に便利です。{zwsp}link:exec.html#simple[単純コマンドの実行]を参照。 + @@ -163,11 +167,14 @@ link:_getopts.html[Getopts 組込みコマンド]で引数付きのオプショ [[sv-path]]+PATH+:: この変数は、{zwsp}link:exec.html#search[コマンドの検索時]にコマンドのありかを示すパスを指定します。 +[[sv-post_prompt_command]]+POST_PROMPT_COMMAND+:: +link:posix.html[POSIX 準拠モード]でない{zwsp}link:interact.html[対話モード]のシェルにおいて、シェルがコマンドを一行読み込むたびに、この変数の値がコマンドとして解釈・実行されます。詳細は{zwsp}link:interact.html#prompt[プロンプト]を参照してください。 + [[sv-ppid]]+PPID+:: この変数の値は、シェルの親プロセスのプロセス ID を表す正の整数です。この変数はシェルの起動時に初期化されます。この変数の値は{zwsp}link:exec.html#subshell[サブシェル]においても変わりません。 [[sv-prompt_command]]+PROMPT_COMMAND+:: -link:posix.html[POSIX 準拠モード]でない{zwsp}link:interact.html[対話モード]のシェルにおいて、シェルが各コマンドのプロンプトを出す直前に、この変数の値がコマンドとして解釈・実行されます。これは、プロンプトを出す直前に毎回 +link:posix.html[POSIX 準拠モード]でない{zwsp}link:interact.html[対話モード]のシェルにおいて、シェルが各コマンドの{zwsp}link:interact.html#prompt[プロンプト]を出す直前に、この変数の値がコマンドとして解釈・実行されます。これは、プロンプトを出す直前に毎回 ifdef::basebackend-html[] pass:[eval -i -- "${PROMPT_COMMAND-}"] endif::basebackend-html[] diff --git a/doc/ja/posix.txt b/doc/ja/posix.txt index feb762db..581542a2 100644 --- a/doc/ja/posix.txt +++ b/doc/ja/posix.txt @@ -35,7 +35,7 @@ POSIX 準拠モードを有効にすると、yash は POSIX の規定にでき - link:builtin.html#types[任意組込みコマンドおよび拡張組込みコマンド]は実行できません。 - いくつかの{zwsp}link:builtin.html[組込みコマンド]で特定のオプションが使えなくなるなど挙動が変わります。特に、長いオプションは使えなくなります。 - link:interact.html[対話モード]でないとき、{zwsp}link:builtin.html#types[特殊組込みコマンド]のオプションやオペランドの使い方が間違っているとシェルは直ちに終了します。また特殊組込みコマンドで代入エラーやリダイレクトエラーが発生したときも直ちに終了します。 -- link:interact.html[対話モード]のプロンプトを出す前に link:params.html#sv-prompt_command[+PROMPT_COMMAND+ 変数]の値を実行しません。{zwsp}link:params.html#sv-ps1[+PS1+ 変数]・{zwsp}link:params.html#sv-ps2[+PS2+ 変数]・{zwsp}link:params.html#sv-ps4[+PS4+ 変数]の値の解釈の仕方が違います。{zwsp}link:params.html#sv-yash_ps1[+YASH_PS1+] など +YASH_+ で始まる名前のプロンプト変数は使用されません。 +- link:interact.html[対話モード]のプロンプトを出す前後に link:params.html#sv-prompt_command[+PROMPT_COMMAND+ 変数]および link:params.html#sv-post_prompt_command[+POST_PROMPT_COMMAND+ 変数]の値を実行しません。{zwsp}link:params.html#sv-ps1[+PS1+ 変数]・{zwsp}link:params.html#sv-ps2[+PS2+ 変数]・{zwsp}link:params.html#sv-ps4[+PS4+ 変数]の値の解釈の仕方が違います。{zwsp}link:params.html#sv-yash_ps1[+YASH_PS1+] など +YASH_+ で始まる名前のプロンプト変数は使用されません。 - link:interact.html#mailcheck[メールチェック]において、ファイルが更新されている場合はファイルが空でも新着メールメッセージを出力します。 // vim: set filetype=asciidoc expandtab: diff --git a/doc/params.txt b/doc/params.txt index 243e86b7..130e642b 100644 --- a/doc/params.txt +++ b/doc/params.txt @@ -145,6 +145,11 @@ This variable specifies the width (the number of character columns) of the terminal screen. The value affects the display of link:lineedit.html[line-editing]. +[[sv-command]]+COMMAND+:: +While a <> is being executed, +this variable is set to the just input command line. It will be unset after +the post-prompt command finishes. + [[sv-command_not_found_handler]]+COMMAND_NOT_FOUND_HANDLER+:: When the shell cannot find a command to be executed, the value of this variable is interpreted and executed instead. @@ -284,6 +289,12 @@ This variable is initialized to +1+ when the shell is started. This variable specifies paths that are searched for a command in link:exec.html#search[command search]. +[[sv-post_prompt_command]]+POST_PROMPT_COMMAND+:: +The shell interprets and executes the value of this variable after reading +each command if the shell is link:interact.html[interactive] and not in +the link:posix.html[POSIXly-correct mode]. +See link:interact.html#prompt[Prompts] for details. + [[sv-ppid]]+PPID+:: The value of this variable is the process ID of the shell's parent process, which is a positive integer. @@ -293,7 +304,7 @@ link:exec.html#subshell[subshell]. [[sv-prompt_command]]+PROMPT_COMMAND+:: The shell interprets and executes the value of this variable before printing -each command prompt if the shell is link:interact.html[interactive] and not in +each command link:interact.html#prompt[prompt] if the shell is link:interact.html[interactive] and not in the link:posix.html[POSIXly-correct mode]. This behavior is equivalent to executing the command ifdef::basebackend-html[] diff --git a/doc/posix.txt b/doc/posix.txt index 5abf88c8..02c4ae33 100644 --- a/doc/posix.txt +++ b/doc/posix.txt @@ -87,8 +87,9 @@ When the POSIXly-correct mode is enabled: arguments or when an error occurs in assignment or redirection with a special built-in. - An link:interact.html[interactive] shell does not execute the - link:params.html#sv-prompt_command[+PROMPT_COMMAND+ variable] before - printing a prompt. + link:params.html#sv-prompt_command[+PROMPT_COMMAND+] or + link:params.html#sv-post_prompt_command[+POST_PROMPT_COMMAND+ variable] + before and after a prompt, respectively. The values of the link:params.html#sv-ps1[+PS1+], link:params.html#sv-ps2[+PS2+], and link:params.html#sv-ps4[+PS4+] variables are parsed differently. diff --git a/input.c b/input.c index 25ef3544..8002f4f5 100644 --- a/input.c +++ b/input.c @@ -64,6 +64,8 @@ static wchar_t *expand_ps1_posix(wchar_t *s) __attribute__((nonnull,malloc,warn_unused_result)); static inline wchar_t get_euid_marker(void) __attribute__((pure)); +static wchar_t *post_prompt_command(wchar_t *line) + __attribute__((nonnull,malloc,warn_unused_result)); /* An input function that inputs from a wide string. * `inputinfo' must be a pointer to a `struct input_wcs_info_T'. @@ -288,6 +290,8 @@ inputresult_T input_interactive(struct xwcsbuf_T *buf, void *inputinfo) #endif if (info->prompttype == 1) info->prompttype = 2; + if (!posixly_correct) + line = post_prompt_command(line); #if YASH_ENABLE_HISTORY add_history(line); #endif @@ -481,6 +485,39 @@ wchar_t get_euid_marker(void) return geteuid() == 0 ? L'#' : L'$'; } +/* Executes $POST_PROMPT_COMMAND, if any. + * `line' is the just input command line, which will be assigned to $COMMAND + * during the execution. The post-prompt command may modify or unset the + * variable. The final value of the variable is returned as a newly malloced + * string. `line' is freed in this function. */ +wchar_t *post_prompt_command(wchar_t *line) +{ + // If `line` ends with a newline, trim it here and append it back later. + size_t linelen = wcslen(line); + bool newline = linelen > 0 && line[linelen - 1] == L'\n'; + if (newline) + line[linelen - 1] = L'\0'; + + open_new_environment(false); + set_positional_parameters((void *[]) { NULL }); + set_variable(L VAR_COMMAND, line, SCOPE_LOCAL, false); + + exec_variable_as_auxiliary_(VAR_POST_PROMPT_COMMAND); + const wchar_t *c = getvar(L VAR_COMMAND); + if (c == NULL) + c = L""; + + xwcsbuf_T linebuf; + wb_init(&linebuf); + wb_cat(&linebuf, c); + if (newline) + wb_wccat(&linebuf, L'\n'); + + close_current_environment(); + + return wb_towcs(&linebuf); +} + /* Unsets O_NONBLOCK flag of the specified file descriptor. * If `fd' is negative, does nothing. * Returns true if successful. On error, `errno' is set and false is returned.*/ diff --git a/share/initialization/common b/share/initialization/common index 405a4255..feafb387 100644 --- a/share/initialization/common +++ b/share/initialization/common @@ -199,6 +199,7 @@ export SHLVL # initialize event handlers COMMAND_NOT_FOUND_HANDLER=() PROMPT_COMMAND=() +POST_PROMPT_COMMAND=() YASH_AFTER_CD=() # define prompt diff --git a/tests/prompt-y.tst b/tests/prompt-y.tst index e9042a4d..524a31b9 100644 --- a/tests/prompt-y.tst +++ b/tests/prompt-y.tst @@ -102,6 +102,14 @@ $ $ __ERR__ +test_Oe 'POST_PROMPT_COMMAND is ignored in POSIX mode' -i +m +POST_PROMPT_COMMAND='echo not printed'; echo >&2 +echo >&2; exit +__IN__ +$ +$ +__ERR__ + ) test_e 'YASH_PSx precedes PSx (non-POSIX)' -i +m @@ -193,6 +201,34 @@ $ 123$ 1 __ERR__ +test_oe 'value of $COMMAND in post-prompt command' -i +m +POST_PROMPT_COMMAND='printf "[%s]\n" "$COMMAND" >&2' +echo foo\ +bar; exit +__IN__ +foobar +__OUT__ +$ $ [echo foo\] +> [bar; exit] +__ERR__ + +test_o 'modifying $COMMAND in post-prompt command' -i +m +POST_PROMPT_COMMAND='COMMAND="$COMMAND; echo post"' +echo foo +exit +__IN__ +foo +post +__OUT__ + +test_O 'unsetting $COMMAND in post-prompt command' -i +m +POST_PROMPT_COMMAND='if [ "$COMMAND" != exit ]; then unset COMMAND; fi' +echo foo\ +bar +exit +echo not reached +__IN__ + test_e '\$ in PS1 and PS2 (non-root)' -i +m PS1='\$ ' PS2='\$_'; echo >&2 e\ diff --git a/tests/run-test.sh b/tests/run-test.sh index 2678b2b7..6faeb3ce 100644 --- a/tests/run-test.sh +++ b/tests/run-test.sh @@ -77,10 +77,10 @@ exec >|"${test_file%.*}.trs" export LC_CTYPE="${LC_ALL-${LC_CTYPE-$LANG}}" export LANG=C export YASH_LOADPATH= # ignore default yashrc -unset -v CDPATH COLUMNS COMMAND_NOT_FOUND_HANDLER DIRSTACK ECHO_STYLE ENV -unset -v FCEDIT HANDLED HISTFILE HISTRMDUP HISTSIZE HOME IFS LC_ALL +unset -v CDPATH COLUMNS COMMAND COMMAND_NOT_FOUND_HANDLER DIRSTACK ECHO_STYLE +unset -v ENV FCEDIT HANDLED HISTFILE HISTRMDUP HISTSIZE HOME IFS LC_ALL unset -v LC_COLLATE LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME LINES MAIL -unset -v MAILCHECK MAILPATH NLSPATH OLDPWD PROMPT_COMMAND +unset -v MAILCHECK MAILPATH NLSPATH OLDPWD POST_PROMPT_COMMAND PROMPT_COMMAND unset -v PS1 PS1R PS1S PS2 PS2R PS2S PS3 PS3R PS3S PS4 PS4R PS4S unset -v RANDOM TERM YASH_AFTER_CD YASH_LE_TIMEOUT YASH_VERSION unset -v A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ diff --git a/variable.h b/variable.h index 671be55c..24dcc13f 100644 --- a/variable.h +++ b/variable.h @@ -1,6 +1,6 @@ /* Yash: yet another shell */ /* variable.h: deals with shell variables and parameters */ -/* (C) 2007-2020 magicant */ +/* (C) 2007-2024 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ extern char **environ; /* variable names */ #define VAR_CDPATH "CDPATH" #define VAR_COLUMNS "COLUMNS" +#define VAR_COMMAND "COMMAND" #define VAR_COMMAND_NOT_FOUND_HANDLER "COMMAND_NOT_FOUND_HANDLER" #define VAR_DIRSTACK "DIRSTACK" #define VAR_ECHO_STYLE "ECHO_STYLE" @@ -57,6 +58,7 @@ extern char **environ; #define VAR_OPTARG "OPTARG" #define VAR_OPTIND "OPTIND" #define VAR_PATH "PATH" +#define VAR_POST_PROMPT_COMMAND "POST_PROMPT_COMMAND" #define VAR_PPID "PPID" #define VAR_PROMPT_COMMAND "PROMPT_COMMAND" #define VAR_PS1 "PS1"