あると便利かな、と思うのだが見つからなかったコマンド。pause

シェルスクリプトでユーザからの入力待ちするコマンド。

DOSにはあるんだけどなぁ...。

つぅことで作ってみた

#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <stdbool.h>
#include <stdlib.h>
#include <getopt.h>
#include <stdint.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

#define DEFAULT_TIMEOUT 0
#define DEFALUT_PROMPT NULL

void usage( FILE* output, int argc, char** argv )
{
	fprintf( output, "Usage: %s [OPTION]...\n", *argv );
	fprintf( output, "Pause untill press key or time out.\n" );
	fprintf( output, "Return pressed key code, or return 255 if timeout.\n" );
	fprintf( output, "\n" );
	fprintf( output, "Mandatory arguments to long options are mandatory for short options too.\n" );
	fprintf( output, "  -c, --stdout               show keycode instead use exit code.\n" );
	fprintf( output, "  -h, --help                 show this help and exit.\n" );
	fprintf( output, "  -p, --prompt[=STRING]      show STRING before pause.\n" );
	fprintf( output, "  -t, --timeout[=NUM]        timeout after NUM sec(0:infinity).\n" );
}

void dummy( int _ ) {}

uint8_t pause( char* prompt, long sec, long usec )
{
	struct termios tm;
	struct termios tm_save;
	
	struct sigaction act;
	struct sigaction act_save;
	struct itimerval itimer;

	ssize_t size;
	uint8_t buf;
	
	// ターミナル情報の保存
	tcgetattr( STDIN_FILENO, &tm_save );
	tm = tm_save;

	// シグナルoff カノニカルoff エコーoff
	tm.c_lflag &= ~ISIG;
	tm.c_lflag &= ~ICANON;
	tm.c_lflag &= ~ECHO;
	tcsetattr( STDIN_FILENO, TCSANOW, &tm );

	// プロンプトの表示
	if( prompt != NULL ) {
		printf( "%s", prompt );
		fflush( stdout );
	}

	// シグナルをpauseを終了させるために使う。
	// ので、handlerは何もしない。
	memset( &act, 0, sizeof( act ) );
	act.sa_handler = dummy;
	if( sigaction( SIGALRM, &act, &act_save ) < 0 ) {
		perror( "sigaction" );
		exit( EXIT_FAILURE );
	}

	// itimerval構造体の初期化。
	itimer.it_value.tv_sec = sec;
	itimer.it_value.tv_usec = usec;
	itimer.it_interval.tv_sec = 0;
	itimer.it_interval.tv_usec = 0;
	if( setitimer( ITIMER_REAL, &itimer, NULL ) < 0 ) {
		perror( "setitimer" );
		exit( EXIT_FAILURE );
	}

	// 一字読み込み
	size = read( STDIN_FILENO, &buf, sizeof(uint8_t) );
	if( size < 0 ) {
		if( errno == EINTR ) {
			buf = -1; // TIMEOUT
		} else {
			perror( "read" );
			exit( EXIT_FAILURE );
		}
	} else if( size == 0 ) {
		buf = 0; // EOF
	}


	// タイマーを殺す。
	itimer.it_value.tv_sec = 0;
	itimer.it_value.tv_usec = 0;
	if( setitimer( ITIMER_REAL, &itimer, NULL ) < 0 ) {
		perror( "setitimer" );
		exit( EXIT_FAILURE );
	}

	// ハンドラを元に戻す。
	//act.sa_flags = SA_RESETHAND;
	if( sigaction( SIGALRM, &act_save, NULL ) < 0 ) {
		perror( "sigaction" );
		exit( EXIT_FAILURE );
	}

	// ターミナル情報を元に戻す
	tcsetattr( STDIN_FILENO, TCSANOW, &tm_save );

	// 値を返す。
	return buf;
}

int main( int argc, char** argv )
{
	bool use_stdout = false;
	char* prompt = DEFALUT_PROMPT;
	int timeout = DEFAULT_TIMEOUT;

	// 引数処理
	while( 1 ) {
		static struct option longopts[] = {
			{ "stdout", no_argument, NULL, 'c' },
			{ "help", no_argument, NULL, 'h' },
			{ "prompt", required_argument, NULL, 'p' },
			{ "timeout", required_argument, NULL, 't' },
			{ 0, 0, 0, 0 }
		};
		int c = getopt_long( argc, argv, "chp:t:", longopts, NULL );
		if( c == -1 ) {
			break;
		}
		switch( c ) {
			case 'c':
				use_stdout = true;
				break;
			case 'h':
				usage( stdout, argc, argv );
				exit( EXIT_SUCCESS );
				break;
			case 'p':
				prompt = optarg;
				break;
			case 't':
				timeout = atoi( optarg );
				break;
			default:
				usage( stderr, argc, argv );
				exit( EXIT_FAILURE );
		}
	}
	if( optind == argc - 1 ) {
		usage( stderr, argc, argv );
		exit( EXIT_FAILURE );
	}

	uint8_t buf = pause( prompt, timeout, 0 );
	if( use_stdout ) {
		printf( "%d\n", buf );
		return 0;
	} else {
		return buf;
	}
}

せっかくC++なのにほとんどCと言う...。ってかこれCでコンパイル通る気がしてきた...。
むだにusageとかつけてみたり。
まぁ、そのusageが妙ちくりんな英語なんですがね。


一時入力があるまでポーズするコマンド。
多分、単品利用ではほとんど無価値かと。
で、似たようなものにreadつーコマンドがあるのですが、readと違って改行しなくても終了します。
で、押下したキーのキーコードを終了ステータスとして返します。
オプションとしては、終了ステータスではなくて、標準出力にキーコードを吐く-c。
あと、一定秒数後にタイムアウトする-tオプション。
入力待ちの前にプロンプトを吐く-pオプション。
あたり。
返り値はtimeoutで255、EOFで0。(ただし、Ctrl-dはキーコードの4になります。

つうこって使用例。

#!/bin/sh
# irm

if [ $# -ne 0 -a -e $1 ]; then
	while true; do
		pause -p "remove file '$1'? (y/N) "
		case $? in
			121|89) # y/Y
				echo
				rm $1
				break
				;;
			110|78|10|3|4) # n/N/Enter/C-c/C-d
				echo
				break
				;;
			*)
				echo $?
				;;
		esac
	done
fi

...Ctrl-Cぐらいは受け付けるべきだったかなぁ?
まぁいいや。
こんんか感じで。rm -i使えという話ですが。
あとrm -iとの違いはEnter押下が必要かどうか。
んー。あまり有用性なかったかも。

まぁ、そんなこんな。
あと、shuffleとかも作ったんでそっちもよろしく。
http://d.hatena.ne.jp/goth_wrist_cut/20071129/1196316532