Skip to content

Commit

Permalink
$POST_PROMPT_COMMAND (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicant authored Apr 20, 2024
2 parents a47afee + b71b641 commit a44e8bf
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 40 deletions.
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions NEWS.ja
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ Yash 更新履歴

## Yash 2.57 (未リリース)

- "$POST_PROMPT_COMMAND" に対応。対話モードでコマンドを読むごとに
変数の値が実行される
- [行編集] nounset オプション有効時に `git config alias.` に続けて
補完をしようとするとエラーが出るのを修正
- [行編集] カーソルがバックスラッシュの直後にある時に補完をすると
Expand Down
1 change: 1 addition & 0 deletions doc/_set.txt
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ This option enables the link:posix.html[POSIXly-correct mode].
When this option is disabled, the <<so-xtrace,x-trace option>> 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.

Expand Down
13 changes: 10 additions & 3 deletions doc/interact.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions doc/ja/_set.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ link:syntax.html#for[For ループ]が{zwsp}link:exec.html#function[関数]の
[[so-traceall]]trace-all::
このオプションは、補助コマンド実行中も <<so-xtrace,x-trace オプション>>を機能させるかどうかを指定します。補助コマンドとは、
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+]
変数の値として定義され、特定のタイミングで解釈・実行されるコマンドです。
Expand Down
5 changes: 4 additions & 1 deletion doc/ja/interact.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
== コマンド履歴
Expand Down
9 changes: 8 additions & 1 deletion doc/ja/params.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ dfn:[変数]とはユーザが自由に代入可能なパラメータです。
[[sv-columns]]+COLUMNS+::
この変数は端末ウィンドウの横幅 (文字数) を指定します。この変数が設定されている場合、デフォルトの横幅ではなくこの変数の値で指定された横幅が{zwsp}link:lineedit.html[行編集]で使われます。

[[sv-command]]+COMMAND+::
<<sv-post_prompt_command,+POST_PROMPT_COMMAND+>> 変数が実行される間、この変数は直前に入力されたコマンドを示します。
+POST_PROMPT_COMMAND+ の実行が終わるとこの変数は消去されます。

[[sv-command_not_found_handler]]+COMMAND_NOT_FOUND_HANDLER+::
シェルが実行しようとしたコマンドが見つからなかったとき、この変数の値がコマンドとして実行されます。不明なコマンドを実行したときに何か別のコマンドを実行させたい時に便利です。{zwsp}link:exec.html#simple[単純コマンドの実行]を参照。
+
Expand Down Expand Up @@ -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:[<code><a href="_eval.html">eval</a> -i -- "${PROMPT_COMMAND-}"</code>]
endif::basebackend-html[]
Expand Down
2 changes: 1 addition & 1 deletion doc/ja/posix.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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:
13 changes: 12 additions & 1 deletion doc/params.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<sv-post_prompt_command,post-prompt command>> 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.
Expand Down Expand Up @@ -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.
Expand All @@ -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[]
Expand Down
5 changes: 3 additions & 2 deletions doc/posix.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
93 changes: 66 additions & 27 deletions input.c
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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'.
Expand Down Expand Up @@ -238,62 +240,66 @@ 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
* and `le_readline', or the "notifyle" option won't work. More precisely,
* `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 */

/* 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 (!posixly_correct)
line = post_prompt_command(line);
#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;
}

Expand Down Expand Up @@ -479,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.*/
Expand Down
1 change: 1 addition & 0 deletions share/initialization/common
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ export SHLVL
# initialize event handlers
COMMAND_NOT_FOUND_HANDLER=()
PROMPT_COMMAND=()
POST_PROMPT_COMMAND=()
YASH_AFTER_CD=()

# define prompt
Expand Down
36 changes: 36 additions & 0 deletions tests/prompt-y.tst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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\
Expand Down
6 changes: 3 additions & 3 deletions tests/run-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 _
Expand Down
4 changes: 3 additions & 1 deletion variable.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit a44e8bf

Please sign in to comment.