more: use getopt_long() to parse options

This commit also includes fix to how initial skip lines and search are
instructed in the code.  Earlier version was pretty close impossible to make
work with getopt_long() and had minor flaw - if both initial skip lines and
search were defined at the same time the skipping did not happen.  That is
now corrected.

Signed-off-by: Sami Kerola <kerolasa@iki.fi>
This commit is contained in:
Sami Kerola 2020-03-28 15:09:00 +00:00
parent 94bece3c22
commit e3cb27f535
No known key found for this signature in database
GPG Key ID: 0D46FEF7E61DBB46
4 changed files with 169 additions and 123 deletions

View File

@ -5,18 +5,33 @@ _more_module()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
case $prev in
'-V')
'-n'|'--lines')
COMPREPLY=( $(compgen -W "number" -- $cur) )
return 0
;;
'-h'|'--help'|'-V'|'--version')
return 0
;;
esac
case $cur in
-*)
OPTS="-d -f -l -p -c -u -s -number -V"
OPTS="
--silent
--logical
--no-pause
--print-over
--clean-print
--squeeze
--plain
--lines
--help
--version
"
COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
return 0
;;
+*)
OPTS="+number +/string"
OPTS="+number +/pattern"
COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
return 0
;;

View File

@ -80,7 +80,7 @@ bin_PROGRAMS += more
dist_man_MANS += text-utils/more.1
more_SOURCES = text-utils/more.c
more_CFLAGS = $(AM_CFLAGS) $(BSD_WARN_CFLAGS)
more_LDADD = $(LDADD)
more_LDADD = $(LDADD) libcommon.la
if HAVE_TINFO
more_LDADD += $(TINFO_LIBS)
more_LDADD += $(TINFO_CFLAGS)

View File

@ -34,7 +34,7 @@
.\"
.\" Copyright (c) 1992 Rik Faith (faith@cs.unc.edu)
.\"
.TH MORE "1" "February 2014" "util-linux" "User Commands"
.TH MORE "1" "March 2020" "util-linux" "User Commands"
.SH NAME
more \- file perusal filter for crt viewing
.SH SYNOPSIS
@ -56,40 +56,50 @@ Options are also taken from the environment variable
.RB ( \- ))
but command-line options will override those.
.TP
.B \-d
.BR \-d , " \-\-silent"
Prompt with "[Press space to continue, 'q' to quit.]",
and display "[Press 'h' for instructions.]" instead of ringing
the bell when an illegal key is pressed.
.TP
.B \-l
.BR \-l , " \-\-logical"
Do not pause after any line containing a
.B \&^L
(form feed).
.TP
.B \-f
.BR \-f , " \-\-no\-pause"
Count logical lines, rather than screen lines (i.e., long lines are not folded).
.TP
.B \-p
.BR \-p , " \-\-print\-over"
Do not scroll. Instead, clear the whole screen and then display the text.
Notice that this option is switched on automatically if the executable is
named
.BR page .
.TP
.B \-c
.BR \-c , " \-\-clean\-print"
Do not scroll. Instead, paint each screen from the top, clearing the
remainder of each line as it is displayed.
.TP
.B \-s
.BR \-s , " \-\-squeeze"
Squeeze multiple blank lines into one.
.TP
.B \-u
.BR \-u , " \-\-plain"
Suppress underlining. This option is silently ignored as backwards
compatibility.
.TP
\fB\-n\fR, \fB\-\-lines \fInumber\fR
Specify the
.b number
of lines per screenful. The
.b number
argument is a positive decimal integer. The
.B \-\-lines
option shall override any values obtained from any other source, such as
number of lines reported by terminal.
.TP
.BI \- number
The screen size to use, in
.I number
of lines.
A numeric option means the same as
.B \-\-lines
option argument.
.TP
.BI + number
Start displaying each file at line

View File

@ -64,6 +64,7 @@
#include <poll.h>
#include <sys/signalfd.h>
#include <paths.h>
#include <getopt.h>
#if defined(HAVE_NCURSESW_TERM_H)
# include <ncursesw/term.h>
@ -126,6 +127,7 @@ struct more_control {
int d_scroll_len; /* number of lines scrolled by 'd' */
int prompt_len; /* message prompt length */
int current_line; /* line we are currently at */
int next_jump; /* number of lines to skip ahead */
char **file_names; /* The list of file names */
int num_files; /* Number of files left to process */
char *shell; /* name of the shell to use */
@ -143,6 +145,7 @@ struct more_control {
char *move_line_down; /* move line down */
char *clear_rest; /* clear rest of screen */
int num_columns; /* number of columns */
char *next_search; /* file beginning search string */
char *previous_search; /* previous search() buf[] item */
struct {
off_t row_num; /* row file position */
@ -166,7 +169,6 @@ struct more_control {
fold_long_lines:1, /* fold long lines */
hard_tabs:1, /* print spaces instead of '\t' */
hard_tty:1, /* is this hard copy terminal (a printer or such) */
jump_at_start:1, /* jump to line N defined at start up */
is_paused:1, /* is output paused */
no_quit_dialog:1, /* suppress quit dialog */
no_scroll:1, /* do not scroll, clear the screen and then display text */
@ -186,54 +188,77 @@ struct more_control {
static void __attribute__((__noreturn__)) usage(void)
{
FILE *out = stdout;
fputs(USAGE_HEADER, out);
fprintf(out, _(" %s [options] <file>...\n"), program_invocation_short_name);
printf("%s", USAGE_HEADER);
printf(_(" %s [options] <file>...\n"), program_invocation_short_name);
fputs(USAGE_SEPARATOR, out);
fputs(_("A file perusal filter for CRT viewing.\n"), out);
printf("%s", USAGE_SEPARATOR);
printf("%s\n", _("A file perusal filter for CRT viewing."));
fputs(USAGE_OPTIONS, out);
fputs(_(" -d display help instead of ringing bell\n"), out);
fputs(_(" -f count logical rather than screen lines\n"), out);
fputs(_(" -l suppress pause after form feed\n"), out);
fputs(_(" -c do not scroll, display text and clean line ends\n"), out);
fputs(_(" -p do not scroll, clean screen and display text\n"), out);
fputs(_(" -s squeeze multiple blank lines into one\n"), out);
fputs(_(" -<number> the number of lines per screenful\n"), out);
fputs(_(" +<number> display file beginning from line number\n"), out);
fputs(_(" +/<string> display file beginning from search string match\n"), out);
fputs(USAGE_SEPARATOR, out);
printf( " --help %s\n", USAGE_OPTSTR_HELP);
printf( " -V, --version %s\n", USAGE_OPTSTR_VERSION);
printf("%s", USAGE_OPTIONS);
printf("%s\n", _(" -d, --silent display help instead of ringing bell"));
printf("%s\n", _(" -f, --logical count logical rather than screen lines"));
printf("%s\n", _(" -l, --no-pause suppress pause after form feed"));
printf("%s\n", _(" -c, --print-over do not scroll, display text and clean line ends"));
printf("%s\n", _(" -p, --clean-print do not scroll, clean screen and display text"));
printf("%s\n", _(" -s, --squeeze squeeze multiple blank lines into one"));
printf("%s\n", _(" -u, --plain suppress underlining and bold"));
printf("%s\n", _(" -n, --lines <number> the number of lines per screenful"));
printf("%s\n", _(" -<number> same as --lines"));
printf("%s\n", _(" +<number> display file beginning from line number"));
printf("%s\n", _(" +/<pattern> display file beginning from pattern match"));
printf("%s", USAGE_SEPARATOR);
printf(USAGE_HELP_OPTIONS(23));
printf(USAGE_MAN_TAIL("more(1)"));
exit(EXIT_SUCCESS);
}
static void arg_parser(struct more_control *ctl, char *s)
static void argscan(struct more_control *ctl, int as_argc, char **as_argv)
{
int seen_num = 0;
int c, opt;
static const struct option longopts[] = {
{ "silent", no_argument, NULL, 'd' },
{ "logical", no_argument, NULL, 'f' },
{ "no-pause", no_argument, NULL, 'l' },
{ "print-over", no_argument, NULL, 'c' },
{ "clean-print", no_argument, NULL, 'p' },
{ "squeeze", no_argument, NULL, 's' },
{ "plain", no_argument, NULL, 'u' },
{ "lines", required_argument, NULL, 'n' },
{ "version", no_argument, NULL, 'V' },
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
while (*s != '\0') {
switch (*s) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (!seen_num) {
ctl->lines_per_screen = 0;
seen_num = 1;
/* Take care of number option and +args. */
for (opt = 0; opt < as_argc; opt++) {
int move = 0;
if (as_argv[opt][0] == '-' && isdigit_string(as_argv[opt] + 1)) {
ctl->lines_per_screen =
strtos16_or_err(as_argv[opt], _("failed to parse number"));
ctl->lines_per_screen = abs(ctl->lines_per_screen);
move = 1;
} else if (as_argv[opt][0] == '+') {
if (isdigit_string(as_argv[opt] + 1)) {
ctl->next_jump = strtos32_or_err(as_argv[opt],
_("failed to parse number")) - 1;
move = 1;
} else if (as_argv[opt][1] == '/') {
free(ctl->next_search);
ctl->next_search = xstrdup(as_argv[opt] + 2);
ctl->search_at_start = 1;
move = 1;
}
ctl->lines_per_screen = ctl->lines_per_screen * 10 + *s - '0';
break;
}
if (move) {
as_argc--;
memmove(as_argv + opt, as_argv + opt + 1, sizeof(char *) * (as_argc - opt));
opt--;
}
}
while ((c = getopt_long(as_argc, as_argv, "dflcpsun:eVh", longopts, NULL)) != -1) {
switch (c) {
case 'd':
ctl->suppress_bell = 1;
break;
@ -254,21 +279,48 @@ static void arg_parser(struct more_control *ctl, char *s)
break;
case 'u':
break;
case '-':
case ' ':
case '\t':
case 'n':
ctl->lines_per_screen = strtou16_or_err(optarg, _("argument error"));
break;
case 'e': /* ignored silently to be posix compliant */
break;
case 'V':
print_version(EXIT_SUCCESS);
exit(EXIT_SUCCESS);
break;
case 'h':
usage();
default:
warnx(_("unknown option -%s"), s);
errtryhelp(EXIT_FAILURE);
break;
}
s++;
}
ctl->num_files = as_argc - optind;
ctl->file_names = as_argv + optind;
}
static void env_argscan(struct more_control *ctl, const char *s)
{
char **env_argv;
int env_argc = 1;
int size = 8;
const char delim[] = { ' ', '\n', '\t', '\0' };
char *str = xstrdup(s);
char *key = NULL, *tok;
env_argv = xmalloc(sizeof(char *) * size);
env_argv[0] = _("MORE environment variable"); /* program name */
for (tok = strtok_r(str, delim, &key); tok; tok = strtok_r(NULL, delim, &key)) {
env_argv[env_argc++] = tok;
if (size < env_argc) {
size *= 2;
env_argv = xrealloc(env_argv, sizeof(char *) * size);
}
}
argscan(ctl, env_argc, env_argv);
/* Reset optind, command line parsing needs this. */
optind = 0;
free(str);
free(env_argv);
}
static void more_fseek(struct more_control *ctl, off_t pos)
@ -1139,15 +1191,15 @@ static int colon_command(struct more_control *ctl, char *filename, int cmd, int
}
/* Skip n lines in the file f */
static void skip_lines(struct more_control *ctl, int n)
static void skip_lines(struct more_control *ctl)
{
int c;
while (n > 0) {
while (ctl->next_jump > 0) {
while ((c = more_getc(ctl)) != '\n')
if (c == EOF)
return;
n--;
ctl->next_jump--;
ctl->current_line++;
}
}
@ -1236,6 +1288,12 @@ static void search(struct more_control *ctl, char buf[], int n)
int saveln, rc;
regex_t re;
if (buf != ctl->previous_search) {
free(ctl->previous_search);
ctl->previous_search = buf;
}
ctl->search_called = 1;
ctl->context.line_num = saveln = ctl->current_line;
ctl->context.row_num = startline;
lncount = 0;
@ -1298,8 +1356,6 @@ static void search(struct more_control *ctl, char buf[], int n)
fputs(_("\nPattern not found\n"), stdout);
more_exit(ctl);
}
free(ctl->previous_search);
ctl->previous_search = NULL;
notfound:
more_error(ctl, _("Pattern not found"));
}
@ -1388,7 +1444,6 @@ static void execute_editor(struct more_control *ctl, char *cmdbuf, char *filenam
static int skip_backwards(struct more_control *ctl, int nlines)
{
int initline;
int retval;
if (nlines == 0)
@ -1404,14 +1459,14 @@ static int skip_backwards(struct more_control *ctl, int nlines)
putp(ctl->erase_line);
putchar('\n');
initline = ctl->current_line - ctl->lines_per_screen * (nlines + 1);
ctl->next_jump = ctl->current_line - ctl->lines_per_screen * (nlines + 1);
if (!ctl->no_scroll)
initline--;
if (initline < 0)
initline = 0;
ctl->next_jump--;
if (ctl->next_jump < 0)
ctl->next_jump = 0;
more_fseek(ctl, 0);
ctl->current_line = 0; /* skip_lines() will make current_line correct */
skip_lines(ctl, initline);
skip_lines(ctl);
if (!ctl->no_scroll)
retval = ctl->lines_per_screen + 1;
else
@ -1569,10 +1624,9 @@ static int more_key_command(struct more_control *ctl, char *filename)
more_error(ctl, _("No previous regular expression"));
break;
}
ctl->run_previous_command++;
ctl->run_previous_command = 1;
/* fallthrough */
case '/':
ctl->search_called = 1;
if (nlines == 0)
nlines++;
erase_to_col(ctl, 0);
@ -1585,9 +1639,8 @@ static int more_key_command(struct more_control *ctl, char *filename)
} else {
ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '/');
fputc('\r', stderr);
free(ctl->previous_search);
ctl->previous_search = xstrdup(cmdbuf);
search(ctl, cmdbuf, nlines);
ctl->next_search = xstrdup(cmdbuf);
search(ctl, ctl->next_search, nlines);
}
retval = ctl->lines_per_screen - 1;
done = 1;
@ -1704,7 +1757,7 @@ static void copy_file(FILE *f)
}
static void display_file(struct more_control *ctl, char *initbuf, int left)
static void display_file(struct more_control *ctl, int left)
{
if (!ctl->current_file)
return;
@ -1712,14 +1765,13 @@ static void display_file(struct more_control *ctl, char *initbuf, int left)
ctl->current_line = 0;
if (ctl->first_file) {
ctl->first_file = 0;
if (ctl->next_jump)
skip_lines(ctl);
if (ctl->search_at_start) {
free(ctl->previous_search);
ctl->previous_search = xstrdup(initbuf);
search(ctl, initbuf, 1);
search(ctl, ctl->next_search, 1);
if (ctl->no_scroll)
left--;
} else if (ctl->jump_at_start)
skip_lines(ctl, ctl->search_at_start);
}
} else if (ctl->argv_position < ctl->num_files && !ctl->no_tty_out)
left =
more_key_command(ctl, ctl->file_names[ctl->argv_position]);
@ -1842,10 +1894,7 @@ static void initterm(struct more_control *ctl)
int main(int argc, char **argv)
{
char *s;
int chr;
int left;
int start_at_line = 0;
char *initbuf = NULL;
struct more_control ctl = {
.first_file = 1,
.fold_long_lines = 1,
@ -1862,52 +1911,24 @@ int main(int argc, char **argv)
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
close_stdout_atexit();
if (argc > 1) {
/* first arg may be one of our standard longopts */
if (!strcmp(argv[1], "--help"))
usage();
if (!strcmp(argv[1], "--version"))
print_version(EXIT_SUCCESS);
}
ctl.num_files = argc;
ctl.file_names = argv;
setlocale(LC_ALL, "");
initterm(&ctl);
/* Auto set no scroll on when binary is called page */
if (!(strcmp(program_invocation_short_name, "page")))
ctl.no_scroll++;
if ((s = getenv("MORE")) != NULL)
env_argscan(&ctl, s);
argscan(&ctl, argc, argv);
initterm(&ctl);
prepare_line_buffer(&ctl);
ctl.d_scroll_len = ctl.lines_per_page / 2 - 1;
if (ctl.d_scroll_len <= 0)
ctl.d_scroll_len = 1;
if ((s = getenv("MORE")) != NULL)
arg_parser(&ctl, s);
while (--ctl.num_files > 0) {
if ((chr = (*++ctl.file_names)[0]) == '-') {
arg_parser(&ctl, *ctl.file_names + 1);
} else if (chr == '+') {
s = *ctl.file_names;
if (*++s == '/') {
ctl.search_at_start = 1;
initbuf = xstrdup(s + 1);
} else {
ctl.jump_at_start = 1;
for (start_at_line = 0; *s != '\0'; s++)
if (isdigit(*s))
start_at_line =
start_at_line * 10 + *s - '0';
--start_at_line;
}
} else
break;
}
/* allow clear_line_ends only if go_home and erase_line and clear_rest strings are
* defined, and in that case, make sure we are in no_scroll mode */
if (ctl.clear_line_ends) {
@ -1947,7 +1968,7 @@ int main(int argc, char **argv)
copy_file(stdin);
else {
ctl.current_file = stdin;
display_file(&ctl, initbuf, left);
display_file(&ctl, left);
}
ctl.no_tty_in = 0;
ctl.print_banner = 1;
@ -1956,7 +1977,7 @@ int main(int argc, char **argv)
while (ctl.argv_position < ctl.num_files) {
checkf(&ctl, ctl.file_names[ctl.argv_position]);
display_file(&ctl, initbuf, left);
display_file(&ctl, left);
ctl.first_file = 0;
ctl.argv_position++;
}