1 /* vi: set sw=4 ts=4: */
3 * Mini less implementation for busybox
6 * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 * This program needs a lot of development, so consider it in a beta stage
27 * - Add more regular expression support - search modifiers, certain matches, etc.
28 * - Add more complex bracket searching - currently, nested brackets are
30 * - Add support for "F" as an input. This causes less to act in
31 * a similar way to tail -f.
32 * - Check for binary files, and prompt the user if a binary file
34 * - Allow horizontal scrolling. Currently, lines simply continue onto
35 * the next line, per the terminal's discretion
38 * - filename is an array and not a pointer because that avoids all sorts
39 * of complications involving the fact that something that is pointed to
40 * will be changed if the pointer is changed.
41 * - the inp file pointer is used so that keyboard input works after
42 * redirected input has been read from stdin
54 /* These are the escape sequences corresponding to special keys */
55 #define REAL_KEY_UP 'A'
56 #define REAL_KEY_DOWN 'B'
57 #define REAL_KEY_RIGHT 'C'
58 #define REAL_KEY_LEFT 'D'
59 #define REAL_PAGE_UP '5'
60 #define REAL_PAGE_DOWN '6'
62 /* These are the special codes assigned by this program to the special keys */
70 /* The escape codes for highlighted and normal text */
71 #define HIGHLIGHT "\033[7m"
72 #define NORMAL "\033[0m"
74 /* The escape code to clear the screen */
75 #define CLEAR "\033[2J"
77 /* Maximum number of lines in a file */
78 #define MAXLINES 10000
80 /* Get height and width of terminal */
81 #define tty_width_height() get_terminal_width_height(0, &width, &height)
83 /* Function prototypes */
84 static void set_tty_cooked(void);
85 static void set_tty_raw(void);
86 static void tless_exit(int code);
87 static int tless_getch(void);
88 static void move_cursor(int x, int y);
89 static void clear_line(void);
90 static void data_readlines(void);
91 static void free_flines(void);
92 #ifdef CONFIG_FEATURE_LESS_FLAGS
93 static int calc_percent(void);
95 static int reverse_percent(int percentage);
96 #ifdef CONFIG_FEATURE_LESS_FLAGS
97 static void m_status_print(void);
98 static void medium_status_print(void);
100 static void status_print(void);
101 static void buffer_print(void);
102 static void buffer_init(void);
103 static void buffer_down(int nlines);
104 static void buffer_up(int nlines);
105 static void buffer_line(int linenum);
106 static void keypress_process(int keypress);
107 static void colon_process(void);
108 static void number_process(int first_digit);
109 #ifdef CONFIG_FEATURE_LESS_FLAGCS
110 static void flag_change(void);
111 static void show_flag_status(void);
113 static void examine_file(void);
114 static void next_file(void);
115 static void previous_file(void);
116 static void first_file(void);
117 static void remove_current_file(void);
118 static void full_repaint(void);
119 static void add_linenumbers(void);
120 static void save_input_to_file(void);
121 #ifdef CONFIG_FEATURE_LESS_MARKS
122 static void add_mark(void);
123 static void goto_mark(void);
125 #ifdef CONFIG_FEATURE_LESS_REGEXP
126 static void regex_process(void);
127 char *process_regex_on_line(char *line, regex_t *pattern);
128 char *insert_highlights(char *line, int start, int end);
129 static void goto_match (int match);
130 static void search_backwards(void);
132 #ifdef CONFIG_FEATURE_LESS_BRACKETS
133 static char opp_bracket (char bracket);
134 static void match_right_bracket (char bracket);
135 static void match_left_bracket (char bracket);
137 int less_main(int argc, char *argv[]);
142 static char filename[256];
143 static char buffer[100][256];
144 static char *flines[MAXLINES];
145 static int current_file = 1;
146 static int line_pos = 0;
147 static int num_flines;
148 static int num_files = 1;
149 static int past_eof = 0;
151 /* Command line options */
152 static int E_FLAG = 0;
153 static int M_FLAG = 0;
154 static int N_FLAG = 0;
155 static int m_FLAG = 0;
156 static int TILDE_FLAG = 0;
158 /* This is needed so that program behaviour changes when input comes from
160 static int inp_stdin = 0;
161 /* This is required so that when a file is requested to be examined after
162 input has come from stdin (e.g. dmesg | less), the input stream from
163 the keyboard still stays the same. If it switched back to stdin, keyboard
164 input wouldn't work. */
165 static int ea_inp_stdin = 0;
167 #ifdef CONFIG_FEATURE_LESS_MARKS
168 static int mark_lines[15][2];
169 static int num_marks = 0;
172 #ifdef CONFIG_FEATURE_LESS_REGEXP
173 static int match_found = 0;
174 static int match_lines[100];
175 static int match_pos = 0;
176 static int num_matches = 0;
177 static int match_backwards = 0;
178 static int num_back_match = 1;
181 /* Needed termios structures */
182 static struct termios term_orig, term_vi;
184 /* File pointer to get input from */
187 /* Reset terminal input to normal */
188 static void set_tty_cooked() {
190 tcsetattr(0, TCSANOW, &term_orig);
193 /* Set terminal input to raw mode */
194 static void set_tty_raw() {
195 tcgetattr(0, &term_orig);
197 term_vi.c_lflag &= (~ICANON & ~ECHO);
198 term_vi.c_iflag &= (~IXON & ~ICRNL);
199 term_vi.c_oflag &= (~ONLCR);
200 term_vi.c_cc[VMIN] = 1;
201 term_vi.c_cc[VTIME] = 0;
202 tcsetattr(0, TCSANOW, &term_vi);
205 /* Exit the program gracefully */
206 static void tless_exit(int code) {
208 /* TODO: We really should save the terminal state when we start,
209 and restore it when we exit. Less does this with the
210 "ti" and "te" termcap commands; can this be done with
217 /* Grab a character from input without requiring the return key. If the
218 character is ASCII \033, get more characters and assign certain sequences
219 special return codes. Note that this function works best with raw input. */
225 input_key[0] = getc(inp);
226 /* Detect escape sequences (i.e. arrow keys) and handle
229 if (input_key[0] == '\033') {
230 input_key[1] = getc(inp);
231 input_key[2] = getc(inp);
233 if (input_key[1] == '[') {
234 if (input_key[2] == REAL_KEY_UP)
236 else if (input_key[2] == REAL_KEY_DOWN)
238 else if (input_key[2] == REAL_KEY_RIGHT)
240 else if (input_key[2] == REAL_KEY_LEFT)
242 else if (input_key[2] == REAL_PAGE_UP)
244 else if (input_key[2] == REAL_PAGE_DOWN)
248 /* The input is a normal ASCII value */
256 /* Move the cursor to a position (x,y), where (0,0) is the
257 top-left corner of the console */
258 static void move_cursor(int x, int y) {
259 printf("\033[%i;%iH", x, y);
262 static void clear_line() {
263 move_cursor(height, 0);
267 static void data_readlines() {
270 char current_line[256];
273 fp = (inp_stdin) ? stdin : bb_xfopen(filename, "rt");
275 for (i = 0; (!feof(fp)) && (i <= MAXLINES); i++) {
276 strcpy(current_line, "");
277 fgets(current_line, 256, fp);
278 bb_xferror(fp, filename);
279 flines[i] = (char *) bb_xstrndup(current_line, (strlen(current_line) + 1) * sizeof(char));
283 /* Reset variables for a new file */
291 inp = fopen(CURRENT_TTY, "r");
297 inp = fopen(CURRENT_TTY, "r");
304 /* Free the file data */
305 static void free_flines() {
309 for (i = 0; i <= num_flines; i++)
313 #ifdef CONFIG_FEATURE_LESS_FLAGS
314 /* Calculate the percentage the current line position is through the file */
316 return ((100 * (line_pos + height - 2) / num_flines) + 1);
320 /* Turn a percentage into a line number */
321 int reverse_percent(int percentage) {
322 double linenum = percentage;
323 linenum = ((linenum / 100) * num_flines) - 1;
327 #ifdef CONFIG_FEATURE_LESS_FLAGS
328 /* Print a status line if -M was specified */
329 static void m_status_print() {
336 printf("%s%s %s%i%s%i%s%i-%i/%i ", HIGHLIGHT, filename, "(file ", current_file, " of ", num_files, ") lines ", line_pos + 1, line_pos + height - 1, num_flines + 1);
338 printf("%s%s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1);
342 printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1);
345 if (line_pos == num_flines - height + 2) {
346 printf("(END) %s", NORMAL);
347 if ((num_files > 1) && (current_file != num_files))
348 printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL);
351 percentage = calc_percent();
352 printf("%i%s %s", percentage, "%", NORMAL);
356 printf("%s%s lines %i-%i/%i (END) ", HIGHLIGHT, filename, line_pos + 1, num_flines + 1, num_flines + 1);
357 if ((num_files > 1) && (current_file != num_files))
358 printf("- Next: %s", files[current_file]);
359 printf("%s", NORMAL);
363 /* Print a status line if -m was specified */
364 static void medium_status_print() {
367 percentage = calc_percent();
370 printf("%s%s %i%s%s", HIGHLIGHT, filename, percentage, "%", NORMAL);
371 else if (line_pos == num_flines - height + 2)
372 printf("%s(END)%s", HIGHLIGHT, NORMAL);
374 printf("%s%i%s%s", HIGHLIGHT, percentage, "%", NORMAL);
378 /* Print the status line */
379 static void status_print() {
381 /* Change the status if flags have been set */
382 #ifdef CONFIG_FEATURE_LESS_FLAGS
386 medium_status_print();
391 printf("%s%s %s", HIGHLIGHT, filename, NORMAL);
393 printf("%s%s%i%s%i%s%s", HIGHLIGHT, "(file ", current_file, " of ", num_files, ")", NORMAL);
395 else if (line_pos == num_flines - height + 2) {
396 printf("%s%s %s", HIGHLIGHT, "(END)", NORMAL);
397 if ((num_files > 1) && (current_file != num_files))
398 printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL);
403 #ifdef CONFIG_FEATURE_LESS_FLAGS
408 /* Print the buffer */
409 static void buffer_print() {
413 if (num_flines >= height - 2) {
416 for (i = 0; i < height - 1; i++)
417 printf("%s", buffer[i]);
423 for (i = 1; i < (height - 1 - num_flines); i++)
425 for (i = 0; i < height - 1; i++)
426 printf("%s", buffer[i]);
431 /* Initialise the buffer */
432 static void buffer_init() {
436 for (i = 0; i < (height - 1); i++)
437 memset(buffer[i], '\0', 256);
439 /* Fill the buffer until the end of the file or the
440 end of the buffer is reached */
441 for (i = 0; (i < (height - 1)) && (i <= num_flines); i++) {
442 strcpy(buffer[i], flines[i]);
445 /* If the buffer still isn't full, fill it with blank lines */
446 for (; i < (height - 1); i++) {
447 strcpy(buffer[i], "");
451 /* Move the buffer up and down in the file in order to scroll */
452 static void buffer_down(int nlines) {
457 if (line_pos + (height - 3) + nlines < num_flines) {
459 for (i = 0; i < (height - 1); i++)
460 strcpy(buffer[i], flines[line_pos + i]);
463 /* As the number of lines requested was too large, we just move
464 to the end of the file */
465 while (line_pos + (height - 3) + 1 < num_flines) {
467 for (i = 0; i < (height - 1); i++)
468 strcpy(buffer[i], flines[line_pos + i]);
472 /* We exit if the -E flag has been set */
473 if (E_FLAG && (line_pos + (height - 2) == num_flines))
478 static void buffer_up(int nlines) {
484 if (line_pos - nlines >= 0) {
486 for (i = 0; i < (height - 1); i++)
487 strcpy(buffer[i], flines[line_pos + i]);
490 /* As the requested number of lines to move was too large, we
491 move one line up at a time until we can't. */
492 while (line_pos != 0) {
494 for (i = 0; i < (height - 1); i++)
495 strcpy(buffer[i], flines[line_pos + i]);
500 /* Work out where the tildes start */
501 tilde_line = num_flines - line_pos + 3;
504 /* Going backwards nlines lines has taken us to a point where
505 nothing is past the EOF, so we revert to normal. */
506 if (line_pos < num_flines - height + 3) {
511 /* We only move part of the buffer, as the rest
513 for (i = 0; i < (height - 1); i++) {
514 if (i < tilde_line - nlines + 1)
515 strcpy(buffer[i], flines[line_pos + i]);
517 if (line_pos >= num_flines - height + 2)
518 strcpy(buffer[i], "~\n");
525 static void buffer_line(int linenum) {
531 if (linenum < 1 || linenum > num_flines) {
533 printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum, NORMAL);
535 else if (linenum < (num_flines - height - 2)) {
536 for (i = 0; i < (height - 1); i++)
537 strcpy(buffer[i], flines[linenum + i]);
541 for (i = 0; i < (height - 1); i++) {
542 if (linenum + i < num_flines + 2)
543 strcpy(buffer[i], flines[linenum + i]);
545 strcpy(buffer[i], (TILDE_FLAG) ? "\n" : "~\n");
548 /* Set past_eof so buffer_down and buffer_up act differently */
553 static void keypress_process(int keypress) {
555 case KEY_DOWN: case 'e': case 'j': case '\015':
559 case KEY_UP: case 'y': case 'k':
563 case PAGE_DOWN: case ' ': case 'z':
564 buffer_down(height - 1);
567 case PAGE_UP: case 'w': case 'b':
568 buffer_up(height - 1);
572 buffer_down((height - 1) / 2);
576 buffer_up((height - 1) / 2);
579 case 'g': case 'p': case '<': case '%':
580 buffer_up(num_flines + 1);
584 buffer_down(num_flines + 1);
590 #ifdef CONFIG_FEATURE_LESS_MARKS
608 save_input_to_file();
613 #ifdef CONFIG_FEATURE_LESS_FLAGS
619 #ifdef CONFIG_FEATURE_LESS_REGEXP
625 goto_match(match_pos + 1);
629 goto_match(match_pos - 1);
637 #ifdef CONFIG_FEATURE_LESS_FLAGCS
646 #ifdef CONFIG_FEATURE_LESS_BRACKETS
647 case '{': case '(': case '[':
648 match_right_bracket(keypress);
650 case '}': case ')': case ']':
651 match_left_bracket(keypress);
660 if (isdigit(keypress))
661 number_process(keypress);
664 static void colon_process() {
668 /* Clear the current line and print a prompt */
672 keypress = tless_getch();
675 remove_current_file();
680 #ifdef CONFIG_FEATURE_LESS_FLAGS
703 static void number_process(int first_digit) {
709 num_input[0] = first_digit;
711 /* Clear the current line, print a prompt, and then print the digit */
713 printf(":%c", first_digit);
715 /* Receive input until a letter is given */
716 while((num_input[i] = tless_getch()) && isdigit(num_input[i])) {
717 printf("%c",num_input[i]);
721 /* Take the final letter out of the digits string */
722 keypress = num_input[i];
725 num = atoi(num_input);
727 /* We now know the number and the letter entered, so we process them */
729 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
733 case KEY_UP: case 'b': case 'w': case 'y': case 'u':
737 case 'g': case '<': case 'G': case '>':
738 if (num_flines >= height - 2)
739 buffer_line(num - 1);
743 buffer_line(reverse_percent(num));
746 #ifdef CONFIG_FEATURE_LESS_REGEXP
748 goto_match(match_pos + num - 1);
757 num_back_match = num;
767 #ifdef CONFIG_FEATURE_LESS_FLAGCS
768 static void flag_change() {
774 keypress = tless_getch();
787 TILDE_FLAG = !TILDE_FLAG;
794 static void show_flag_status() {
801 keypress = tless_getch();
811 flag_val = TILDE_FLAG;
825 printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val, NORMAL);
829 static void examine_file() {
835 fgets(filename, 256, inp);
837 /* As fgets adds a newline to the end of an input string, we
839 newline_offset = strlen(filename) - 1;
840 filename[newline_offset] = '\0';
842 files[num_files] = bb_xstrndup(filename, (strlen(filename) + 1) * sizeof(char));
843 current_file = num_files + 1;
854 static void next_file() {
855 if (current_file != num_files) {
857 strcpy(filename, files[current_file - 1]);
865 printf("%s%s%s", HIGHLIGHT, "No next file", NORMAL);
869 static void previous_file() {
870 if (current_file != 1) {
872 strcpy(filename, files[current_file - 1]);
881 printf("%s%s%s", HIGHLIGHT, "No previous file", NORMAL);
885 static void first_file() {
886 if (current_file != 1) {
888 strcpy(filename, files[current_file - 1]);
896 static void remove_current_file() {
900 if (current_file != 1) {
902 for (i = 3; i <= num_files; i++)
903 files[i - 2] = files[i - 1];
909 for (i = 2; i <= num_files; i++)
910 files[i - 2] = files[i - 1];
917 static void full_repaint() {
919 int temp_line_pos = line_pos;
922 buffer_line(temp_line_pos);
926 /* This adds line numbers to every line, as the -N flag necessitates */
927 static void add_linenumbers() {
929 char current_line[256];
932 for (i = 0; i <= num_flines; i++) {
933 safe_strncpy(current_line, flines[i], 256);
934 flines[i] = xrealloc(flines[i], strlen(current_line) + 7 );
935 sprintf(flines[i],"%5d %s", i+1, current_line);
939 static void save_input_to_file() {
941 char current_line[256];
946 printf("Log file: ");
947 fgets(current_line, 256, inp);
948 current_line[strlen(current_line) - 1] = '\0';
949 if (strlen(current_line)) {
950 fp = bb_xfopen(current_line, "w");
951 for (i = 0; i < num_flines; i++)
952 fprintf(fp, "%s", flines[i]);
957 printf("%sNo log file%s", HIGHLIGHT, NORMAL);
960 #ifdef CONFIG_FEATURE_LESS_MARKS
961 static void add_mark() {
968 letter = tless_getch();
970 if (isalpha(letter)) {
971 mark_line = line_pos;
973 /* If we exceed 15 marks, start overwriting previous ones */
977 mark_lines[num_marks][0] = letter;
978 mark_lines[num_marks][1] = line_pos;
983 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
987 static void goto_mark() {
993 printf("Go to mark: ");
994 letter = tless_getch();
995 if (isalpha(letter)) {
996 for (i = 0; i <= num_marks; i++)
997 if (letter == mark_lines[i][0]) {
998 buffer_line(mark_lines[i][1]);
1001 if ((num_marks == 14) && (letter != mark_lines[14][0])) {
1003 printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL);
1008 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
1013 #ifdef CONFIG_FEATURE_LESS_REGEXP
1014 /* The below two regular expression handler functions NEED development. */
1016 /* Get a regular expression from the user, and then go through the current
1017 file line by line, running a processing regex function on each one. */
1018 static void regex_process() {
1020 char uncomp_regex[100];
1021 char current_line[256];
1026 /* Reset variables */
1027 match_lines[0] = -1;
1032 pattern = (regex_t *) malloc(sizeof(regex_t));
1033 memset(pattern, 0, sizeof(regex_t));
1035 /* Get the uncompiled regular expression from the user */
1037 if (match_backwards)
1041 scanf("%s", uncomp_regex);
1043 /* Compile the regex and check for errors */
1044 xregcomp(pattern, uncomp_regex, 0);
1046 /* Run the regex on each line of the current file here */
1047 for (i = 0; i <= num_flines; i++) {
1048 strcpy(current_line, process_regex_on_line(flines[i], pattern));
1049 flines[i] = (char *) bb_xstrndup(current_line, sizeof(char) * (strlen(current_line)+1));
1059 if ((match_lines[0] != -1) && (num_flines > height - 2))
1060 buffer_line(match_lines[0]);
1065 char *process_regex_on_line(char *line, regex_t *pattern) {
1066 /* This function takes the regex and applies it to the line.
1067 Each part of the line that matches has the HIGHLIGHT
1068 and NORMAL escape sequences placed around it by
1069 insert_highlights, and then the line is returned. */
1072 char *line2 = (char *) malloc((sizeof(char) * (strlen(line) + 1)) + 64);
1075 memset(sub_line, 0, 256);
1076 strcpy(line2, line);
1077 regmatch_t match_structs;
1080 match_status = regexec(pattern, line2, 1, &match_structs, 0);
1082 while (match_status == 0) {
1084 memset(sub_line, 0, 256);
1086 if (match_found == 0)
1089 line2 = insert_highlights(line2, match_structs.rm_so + prev_eo, match_structs.rm_eo + prev_eo);
1090 if (match_structs.rm_eo + 11 + prev_eo < strlen(line2))
1091 strcat(sub_line, line2 + match_structs.rm_eo + 11 + prev_eo);
1093 prev_eo += match_structs.rm_eo + 11;
1094 match_status = regexec(pattern, sub_line, 1, &match_structs, REG_NOTBOL);
1100 char *insert_highlights (char *line, int start, int end) {
1102 char *new_line = (char *) malloc((sizeof(char) * (strlen(line) + 1)) + 10);
1103 memset(new_line, 0, ((sizeof(char) * (strlen(line) + 1)) + 10));
1104 strncat(new_line, line, start);
1105 strcat(new_line, HIGHLIGHT);
1106 strncat(new_line, line + start, end - start);
1107 strcat(new_line, NORMAL);
1108 strncat(new_line, line + end, strlen(line) - end);
1113 static void goto_match(int match) {
1115 /* This goes to a specific match - all line positions of matches are
1116 stored within the match_lines[] array. */
1117 if ((match < num_matches) && (match >= 0)) {
1118 buffer_line(match_lines[match]);
1123 static void search_backwards() {
1125 int current_linepos = line_pos;
1128 match_backwards = 1;
1131 for (i = 0; i < num_matches; i++) {
1132 if (match_lines[i] > current_linepos) {
1133 buffer_line(match_lines[i - num_back_match]);
1138 /* Reset variables */
1139 match_backwards = 0;
1145 #ifdef CONFIG_FEATURE_LESS_BRACKETS
1147 static char opp_bracket (char bracket) {
1168 static void match_right_bracket(char bracket) {
1170 int bracket_line = -1;
1173 if (strchr(flines[line_pos], bracket) == NULL) {
1175 printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL);
1178 for (i = line_pos + 1; i < num_flines; i++) {
1179 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1185 if (bracket_line == -1) {
1187 printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
1190 buffer_line(bracket_line - height + 2);
1195 static void match_left_bracket (char bracket) {
1197 int bracket_line = -1;
1200 if (strchr(flines[line_pos + height - 2], bracket) == NULL) {
1202 printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL);
1203 printf("%s", flines[line_pos + height]);
1207 for (i = line_pos + height - 2; i >= 0; i--) {
1208 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1214 if (bracket_line == -1) {
1216 printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
1219 buffer_line(bracket_line);
1226 int less_main(int argc, char **argv) {
1228 unsigned long flags;
1231 flags = bb_getopt_ulflags(argc, argv, "EMNm~");
1232 E_FLAG = (flags & 1);
1233 M_FLAG = (flags & 2);
1234 N_FLAG = (flags & 4);
1235 m_FLAG = (flags & 8);
1236 TILDE_FLAG = (flags & 16);
1244 if (ttyname(STDIN_FILENO) == NULL)
1247 bb_error_msg("Missing filename");
1252 strcpy(filename, (inp_stdin) ? "stdin" : files[0]);
1259 keypress = tless_getch();
1260 keypress_process(keypress);