ed_1.21.1/0000775000175000017500000000000000000000000012043 5ustar dogslegdogsleged_1.21.1/AUTHORS0000644000175000017500000000164314357630255013166 0ustar dogslegdogslegSince 2006 GNU ed is maintained by Antonio Diaz Diaz. Before version 0.3, GNU ed and its man page were written and maintained (sic) by Andrew L. Moore. The original info page and GNUification of the code were graciously provided by Franois Pinard. ------------------- GNU ed THANKS file - last updated on 15 November 1994. GNU ed originated with the editor algorithm from Brian W. Kernighan & P. J. Plauger's wonderful book "Software Tools in Pascal", Addison-Wesley, 1981. GNU ed has also benefitted from the contributions of numerous people who reported problems, suggested various improvements or submitted actual code. Among these are the following: Eric Backus Karl Berry Theo Deraadt Kaveh R. Ghazi Mike Haertel Franois Pinard Rodney Ruddock ed_1.21.1/ChangeLog0000644000175000017500000003727314770111771013674 0ustar dogslegdogsleg2025-03-24 Antonio Diaz Diaz * Version 1.21.1 released. * buffer.c: Remove unused and . (Reported by Michael Mikonos and Alexander Jones). * signal.c (window_lines): Read initial size from LINES. (Suggested by Artyom Bologov). * Rename 'line_t' to 'line_node' and 'undo_t' to 'undo_atom'. 2025-01-05 Antonio Diaz Diaz * Version 1.21 released. * io.c (read_file, write_file): Ignore exit status of shell command. Bug introduced in version 1.6. (Reported by Andrew L. Moore). * ed.h: Replace enum Bool with stdbool.h to fix compilation in C23. (Reported by Alexander Jones). * io.c (read_stream): Suppress 'Newline inserted/appended' with '-s'. (Reported by Artyom Bologov). * ed.texi: New chapter 'Syntax of command-line arguments'. 2024-04-22 Antonio Diaz Diaz * Version 1.20.2 released. * main_loop.c (command_s): Fix g/x/s/x/x, which failed to skip the final newline, printing lines twice. (Reported by Douglas McIlroy). 2024-02-14 Antonio Diaz Diaz * Version 1.20.1 released. * io.c (write_file): Don't create missing intermediate directories. 2024-01-19 Antonio Diaz Diaz * Version 1.20 released. * Implement options '+line', '+/RE', and '+?RE'. (Suggested by Matthew Polk and John Cowan). * New option '--unsafe-names'. * main.c: (may_access_filename): Reject file names ending with a slash. (print_filename): New function for file names with control chars. * buffer.c (warned, set_warned): New functions. * main_loop.c: Intervening cmds don't make a second 'e' or 'q' fail. (get_filename): Add tilde expansion. (Suggested by John Cowan). Warn on first modification of buffer loaded from read-only file. (Suggested by Dan Jacobson). * io.c (write_file): Create missing intermediate directories. * ed.texi: Improve descriptions of commands 'e', 'f', 'q'. * main.c, ed.texi: Improve description of exit status. * configure, Makefile.in: New variable 'MAKEINFO'. * INSTALL: Document use of CFLAGS+='--std=c99 -D_POSIX_C_SOURCE=2'. 2023-01-11 Antonio Diaz Diaz * Version 1.19 released. * main_loop.c (exec_command): Fix commands 'e', 'E'; they did set the 'modified' flag if file not found. (Reported by Harry Graf). (main_loop): Print script error line to stdout instead of stderr. * Change long name of option '-s' to '--script'. (Suggested by Andrew L. Moore). * Assign short name '-q' to options '--quiet' and '--silent'. * main.c (show_strerror) Use '!quiet' to enable diagnostics. * Do not process file names for backslash escapes. (Suggested by Andrew L. Moore). * ed.texi: Document 0 as starting point for searches '0;/RE/'. Document how to achieve the effect of ex style '!' filtering. 2022-02-04 Antonio Diaz Diaz * Version 1.18 released. * New option '--strip-trailing-cr'. * main_loop.c (get_shell_command): Flush stdout after printing cmd. (Reported by Sren Tempel). * signal.c (sighup_handler): Fix a memory leak just before exiting. * carg_parser.c (ap_init): Likewise. (Both reported by Xos Vzquez Prez). * io.c (read_file, write_file): Check ptr returned by strip_escapes. * main_loop.c (get_shell_command, exec_command): Likewise. * main_loop.c (get_shell_command): Remove backslash from escaped '%'. (Reported by Martin Thomsen). * main_loop.c, regex.c: Implement case-insensitive REs. * regex.c (compile_regex): Don't overwrite previous regex if error. * buffer.c (push_undo_atom): Fail if stack grows larger than INT_MAX. (too_many_lines): Fail if buffer grows larger than INT_MAX lines. * global.c (set_active_node): Fail if list grows larger than INT_MAX. * signal.c (resize_buffer): Fail if a line grows longer than INT_MAX. * io.c (read_file): Return -2 for fatal errors. * main_loop.c (main_loop): Set error status if fatal error from main. * main.c [restricted_]: New message "Directory access restricted". * ed.texi: New chapter "The 's' Command". * COPYING: Restored. (I forgot to do it in 1.11). * TODO: Removed. 2021-01-06 Antonio Diaz Diaz * Version 1.17 released. * main_loop.c (exec_global): Make commands 'q' and 'Q' work in a global command. (Reported by J. A. Harris). * New option '-E, --extended-regexp'. (Suggested by Shawn Wagner). * io.c (read_stream_line, write_stream): Add filename parameter. Print the file name in case of error. (Reported by Dan Jacobson). * global.c: Integrate 'resize_line_buffer' into 'set_active_node'. * buffer.c: Integrate 'resize_undo_buffer' into 'push_undo_atom'. 2020-02-20 Antonio Diaz Diaz * Version 1.16 released. * regex.c (line_replace): Accept 's/^/#/g' as valid. (Reported by Bjoern Wibben). * main_loop.c: Remove length limit of prompt string. (Reported by Tim Chase). * main.c: Set a valid invocation_name even if argc == 0. * ed.texi: Extended operators depend on regex implementation. (Reported by Brian Zwahr). * ed.texi: Several fixes and improvements. 2019-01-01 Antonio Diaz Diaz * Version 1.15 released. * io.c (print_line): Make command 'l' print '\\' before every '$' within the text. (Reported by Ori Avtalion). * main_loop.c (extract_addresses): Fix address ',,' to mean '$,$' instead of '1,$'. (Reported by Matthieu Felix). * regex.c (extract_replacement): Allow newlines even if global. * main_loop.c (exec_command): Make command 'c' reject address 0. * ed.texi: Minor fixes. * configure: Accept appending to CFLAGS; 'CFLAGS+=OPTIONS'. 2017-02-22 Antonio Diaz Diaz * Version 1.14.2 released. * main.c (show_strerror) Revert to using '!scripted' instead of 'verbose' to enable diagnostics. 2017-01-10 Antonio Diaz Diaz * Version 1.14.1 released. * Print counts, messages, '?' and '!' to stdout instead of stderr. * buffer.c (append_lines): Fix current address after empty 'i'. * regex.c (get_compiled_regex): Fix crash caused by invalid free introduced in ed 1.14. (Reported by Hanno Bck). (set_subst_regex): Treat missing delimiters consistently. (extract_replacement): Don't replace 'a' with '%' in 's/a/%'. Fix infinite loop with EOF in the middle of a replacement. Don't accept newlines in replacement in a global command. Last delimiter can't be omitted if not last in command list. (search_and_replace): Set current address to last line modified. * main_loop.c (extract_addresses): Fix address offsets; '3 ---- 2' was calculated as -2 instead of 1. Accept ranges with the first address omitted. (exec_command): Fix current address after empty replacement text in command 'c'. Don't clear the modified status after writing the buffer to a shell command. (Reported by Jrme Frgacic). (get_command_suffix): Don't allow repeated print suffixes. (command_s): Accept suffixes in any order. Don't allow multiple count suffixes. 'sp' now toggles all print suffixes. (main_loop): Make EOF on stdin behave as command 'q'. * ed.texi: Fix the description of commands 'acegijkmqrsuw'. Document that ed allows any combination of print suffixes. * testsuite: Improve most tests. Simplify bug reporting. * configure: Avoid warning on some shells when testing for gcc. * Makefile.in: Detect the existence of install-info. 2016-01-24 Antonio Diaz Diaz * Version 1.13 released. * buffer.c (put_sbuf_line): Fix a memory leak. * io.c (read_file, write_file): Close file on error. (Both issues reported by Cdric Picard). 2015-07-04 Antonio Diaz Diaz * Version 1.12 released. * ed.texi: Remove extra spaces from some commands. 2015-03-30 Antonio Diaz Diaz * Version 1.11 released. * main_loop.c (exec_command): Fix command 'z'. (zN printed N + 1 lines). * ed.texi: Document the window size used by the command 'z'. * Makefile.in: New targets 'install*-compress'. * Restore original copyright and license notices in the code. I assigned to the FSF the copyright on changes made to the part of ed already copyrighted by the FSF, which seems to be just the manual. 2014-01-22 Antonio Diaz Diaz * Version 1.10 released. * ed.texinfo: Rename to ed.texi. 2013-06-18 Antonio Diaz Diaz * Version 1.9 released. * check.sh: Don't feed shell scripts to ed. * configure: Options now accept a separate argument. 2013-04-23 Antonio Diaz Diaz * Version 1.8 released. * io.c (get_tty_line): Remove "double EOF" behavior. 2012-10-09 Antonio Diaz Diaz * Version 1.7 released. * main.c (main): Set invocation_name before calling show_error. * Change quote characters in messages as advised by GNU Standards. * ed.texinfo: Fix description of address offsets. * ed.texinfo: Fix a link to the Bash manual. * configure: Rename 'datadir' to 'datarootdir'. * Makefile.in: New target 'install-bin'. 2012-01-01 Antonio Diaz Diaz * Version 1.6 released. * io.c (put_tty_line): Null characters where incorrectly shown by the command 'l'. (Reported by Martin Guy). * io.c (read_stream): Fix the condition deciding when to show the message "Newline appended". * main_loop.c (exec_command): The 'modified' flag is now set when reading a non-empty file into an empty buffer. * regex.c (translit_text): Fix typo that prevented using NUL characters in regular expressions. * main_loop.c (exec_command): Return ERR if 'system' can't create a shell process. * main_loop.c (main_loop): Flush stdout/stderr before reading a new command. * buffer.c (put_sbuf_line): Add size parameter. * ed.1: Man page is now generated with 'help2man'. * ed.1: All command-line options are now documented in the man page. * Restore copyright notices of Andrew L. Moore. It seems Andrew granted some permissions but never assigned copyright to the FSF. 2010-08-30 Antonio Diaz Diaz * Version 1.5 released. * buffer.c (append_lines): Fix commands 'a', 'c', and 'i'. (When used in a global command list, the commands following them in the list were ignored). * main_loop.c (exec_command): Fix command 'e'. (It quitted when invoked a second time with a modified buffer). * New option '-r, --restricted'. * 'red' has been converted to a script invoking 'ed --restricted'. * Description of ed in the manual has been changed. * testsuite: Modify some tests and remove obsolete POSIX tests. * main_loop.c: Make variable 'ibufp' local to main_loop. * Define type bool to make clear which functions and variables are Boolean. * Add 'const' to all pointer declarations accepting it. * regex.c (replace_matching_text): Make se_max an enum. * signal.c: Include termios.h. * Convert C99 style comments '//' to C89 style comments '/* */'. * ed.texinfo: Fix an erratum. * Change copyright holder from Andrew, Antonio to the FSF. (This change was later discovered to be wrong. See 1.6 and 1.11). 2009-07-10 Antonio Diaz Diaz * Version 1.4 released. * buffer.c, main_loop.c: Undo now restores the modified status. * regex.c (search_and_replace): Fix a race condition with user interrupt. * signal.c: Add new functions 'resize_line_buffer' and 'resize_undo_buffer' to definitively fix the aliasing warnings. * Some minor corrections have been made to the manual. 2009-05-24 Antonio Diaz Diaz * Version 1.3 released. * carg_parser.c (ap_resize_buffer): An aliasing related segfault that only occurs when overoptimizing with GCC on some architectures (alpha, sparc) has been (hopefully) fixed. * signal.c (resize_buffer): Likewise. 2009-01-31 Antonio Diaz Diaz * Version 1.2 released. * configure: Locale has been fixed to 'C'. * Makefile.in: Man page is now installed by default. * 'make install-info' should now work on Debian and OS X. * ed.texinfo: Update license to GFDL version 1.3 or later. 2008-10-14 Antonio Diaz Diaz * Version 1.1 released. * configure: Quote arguments stored in config.status. 2008-08-21 Antonio Diaz Diaz * Version 1.0 released. * configure: New option '--program-prefix'. * signal.c (strip_escapes): Fix a buffer overflow. * signal.c (resize_buffer): Fix a pointer aliasing warning. 2008-02-24 Antonio Diaz Diaz * Version 0.9 released. * signal.c (sighup_handler): Return 0 if no error. * Arg_parser updated to 1.1. 2007-08-18 Antonio Diaz Diaz * Version 0.8 released. * check.sh: Exit unsuccesfully in case of error. * ed.1: Fix some minor problems in the manual page. * ed.texinfo: Add 21 kB of legalese (fdl.texinfo). 2007-07-18 Antonio Diaz Diaz * Version 0.7 released. * buffer.c (dec_addr): Return correct address when wrapping. 2007-06-29 Antonio Diaz Diaz * Version 0.6 released. * signal.c (sigwinch_handler, set_signal): Fix two minor compatibility problems. * main_loop.c (main_loop): Fix an infinite loop when reading an empty script. * Update license to GPL version 3 or later. (This change was later discovered to be wrong. See 1.5 and 1.18). 2007-03-09 Antonio Diaz Diaz * Version 0.5 released. * main_loop.c (next_addr): '%' reimplemented as it was in ed 0.2. 2007-01-15 Antonio Diaz Diaz * Version 0.4 released. * Fix some minor problems in the testsuite. 2006-11-11 Antonio Diaz Diaz * Version 0.3 released. * buffer.c (open_sbuf): Fix symlink vulnerability using 'tmpfile'. * signal.c: Fix signal handling for SIGINT. * main_loop.c (exec_command): Modify commands 'c' and 'i' to treat address 0 as a synonym for address 1, as per POSIX. * The pause mode has been removed. (Suggested by Karl Berry). * New option '-l, --loose-exit-status'. (Suggested by Karl Berry). * New option '-v, --verbose'. * carg_parser.c: New argument parser that replaces 'getopt_long'. * 'configure' and 'Makefile.in' have been replaced. * Remove recursive make for testsuite. * Create directory 'doc'. * Remove all pre ISO C89 code. * Remove all global variables. * ed.texinfo: Add the changes from Andrew and some mine. Sun Jun 26 22:21:59 1994 Andrew L. Moore * GNU ed 0.2 release. * main.c (yank_lines): Added yank buffer. A range of lines may be cut ('d') to or yanked ('y') from a yank buffer. Lines in the buffer may be put ('x') after the addressed line (. by default). * main.c (display_lines): Page output of listed ('l') lines if isatty(0). * main.c (main): Replaced isatty(0) with is_regular_file(). Errors in piped scripts, as opposed to regular scripts or here documents, do not force ed to exit. * Capitilize error messages per the standard. Wed Jun 22 01:06:11 1994 Andrew L. Moore * ed.h: Generic definition of INT_MAX * signal.c: Added #ifndef SIG_ERR Tue Apr 19 10:52:51 1994 Andrew L. Moore * Version 0.1. Initial release for GNU. * main.c (exec_command): Add comment command '#'. Mon Mar 21 21:58:11 PST 1994 Andrew L. Moore * Use umask 077 to open buffer file. Sat Mar 19 14:06:52 PST 1994 Andrew L. Moore * Removed problematic DES and insque support. Wed Jan 19 20:42:50 PST 1994 Andrew L. Moore * Added reliable signal(2) for SysV. Dec 1993 Franois Pinard * GNUified ed. Copyright (C) 1993 Franois Pinard Copyright (C) 1994 Andrew L. Moore Copyright (C) 2006-2025 Antonio Diaz Diaz. This file is a collection of facts, and thus it is not copyrightable, but just in case, you have unlimited permission to copy, distribute, and modify it. ed_1.21.1/main_loop.c0000644000175000017500000007245214767570754014261 0ustar dogslegdogsleg/* GNU ed - The GNU line editor. Copyright (C) 1993, 1994 Andrew L. Moore, Talke Studio Copyright (C) 2006-2025 Antonio Diaz Diaz. 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include "ed.h" enum Status { QUIT = -1, ERR = -2, EMOD = -3, FATAL = -4 }; static const char * const inv_com_suf = "Invalid command suffix"; static const char * const inv_mark_ch = "Invalid mark character"; static const char * const no_cur_fn = "No current filename"; static const char * const no_prev_com = "No previous command"; static const char * def_filename = ""; /* default filename */ static char errmsg[80] = ""; /* error message buffer */ static const char * prompt_str = "*"; /* command prompt */ static int first_addr = 0, second_addr = 0; static bool prompt_on = false; /* show command prompt */ static bool read_only = false; /* loaded file is not writable */ static bool verbose = false; /* print all error messages */ int first_e_command( const char * const filename ) { return read_file( filename, 0, &read_only ); } void invalid_address( void ) { set_error_msg( "Invalid address" ); } bool set_def_filename( const char * const s ) { static char * buf = 0; /* filename buffer */ static int bufsz = 0; /* filename buffer size */ const int len = strlen( s ); if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return false; memcpy( buf, s, len + 1 ); def_filename = buf; read_only = false; return true; } const char * error_msg( void ) { return errmsg; } void set_error_msg( const char * const msg ) { strncpy( errmsg, msg, sizeof errmsg ); errmsg[sizeof(errmsg)-1] = 0; } bool set_prompt( const char * const s ) { static char * buf = 0; /* prompt buffer */ static int bufsz = 0; /* prompt buffer size */ const int len = strlen( s ); if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return false; memcpy( buf, s, len + 1 ); prompt_str = buf; prompt_on = true; return true; } void set_verbose( void ) { verbose = true; } static const line_node * mark[26]; /* line markers */ static int markno; /* line marker count */ static bool mark_line_node( const line_node * const lp, int c ) { c -= 'a'; if( c < 0 || c >= 26 ) { set_error_msg( inv_mark_ch ); return false; } if( !mark[c] ) ++markno; mark[c] = lp; return true; } void unmark_line_node( const line_node * const lp ) { int i; for( i = 0; markno && i < 26; ++i ) if( mark[i] == lp ) { mark[i] = 0; --markno; } } /* return address of a marked line */ static int get_marked_node_addr( int c ) { c -= 'a'; if( c < 0 || c >= 26 ) { set_error_msg( inv_mark_ch ); return -1; } return get_line_node_addr( mark[c] ); } /* return pointer to copy of shell command in the command buffer */ static const char * get_shell_command( const char ** const ibufpp ) { static char * buf = 0; /* temporary buffer */ static int bufsz = 0; static char * shcmd = 0; /* shell command buffer */ static int shcmdsz = 0; /* shell command buffer size */ static int shcmdlen = 0; /* shell command length */ int i = 0, len = 0; bool replacement = false; /* true if '!' or '%' are replaced */ if( restricted() ) { set_error_msg( "Shell access restricted" ); return 0; } if( !get_extended_line( ibufpp, &len, true ) ) return 0; if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return 0; if( **ibufpp != '!' ) buf[i++] = '!'; /* prefix command w/ bang */ else /* replace '!' with the previous command */ { if( shcmdlen <= 0 || ( traditional() && !shcmd[1] ) ) { set_error_msg( no_prev_com ); return 0; } memcpy( buf, shcmd, shcmdlen ); /* bufsz >= shcmdlen */ i += shcmdlen; ++*ibufpp; replacement = true; } while( **ibufpp != '\n' ) { if( **ibufpp == '%' ) /* replace '%' with the default filename */ { if( !def_filename[0] ) { set_error_msg( no_cur_fn ); return 0; } len = strlen( def_filename ); if( !resize_buffer( &buf, &bufsz, i + len ) ) return 0; memcpy( buf + i, def_filename, len ); i += len; ++*ibufpp; replacement = true; } else /* copy char or escape sequence unescaping any '%' */ { char ch = *(*ibufpp)++; if( !resize_buffer( &buf, &bufsz, i + 2 ) ) return 0; if( ch != '\\' ) { buf[i++] = ch; continue; } /* normal char */ ch = *(*ibufpp)++; if( ch != '%' ) buf[i++] = '\\'; buf[i++] = ch; } } if( **ibufpp == '\n' ) ++*ibufpp; /* skip newline for global */ if( !resize_buffer( &shcmd, &shcmdsz, i + 1 ) ) return 0; memcpy( shcmd, buf, i ); shcmd[i] = 0; shcmdlen = i; if( replacement ) { printf( "%s\n", shcmd + 1 ); fflush( stdout ); } return shcmd; } static void skip_blanks( const char ** const ibufpp ) { while( isspace( (unsigned char)**ibufpp ) && **ibufpp != '\n' ) ++*ibufpp; } /* return pointer to copy of filename in the command buffer */ static const char * get_filename( const char ** const ibufpp, const bool traditional_f_command ) { static char * buf = 0; static int bufsz = 0; skip_blanks( ibufpp ); if( **ibufpp == '\n' ) { if( !traditional_f_command && !def_filename[0] ) { set_error_msg( no_cur_fn ); return 0; } ++*ibufpp; return ""; /* skip newline for global */ } const char * hd = 0; int hdsize = 0, size = 0; if( !get_extended_line( ibufpp, &size, true ) ) return 0; if( **ibufpp == '!' ) { ++*ibufpp; return get_shell_command( ibufpp ); } if( **ibufpp == '~' && (*ibufpp)[1] == '/' ) { hd = home_directory(); if( hd && hd[0] ) { hdsize = strlen( hd ); --size; ++*ibufpp; } } if( hdsize + size > path_max( 0 ) ) { set_error_msg( "Filename too long" ); return 0; } if( !resize_buffer( &buf, &bufsz, hdsize + size + 1 ) ) return 0; if( hdsize > 0 ) memcpy( buf, hd, hdsize ); int i; for( i = hdsize; i < hdsize + size && **ibufpp != '\n'; ++i, ++*ibufpp ) buf[i] = **ibufpp; buf[i] = 0; if( **ibufpp == '\n' ) ++*ibufpp; /* skip newline for global */ return may_access_filename( buf ) ? buf : 0; } /* convert a string to int with out_of_range detection */ static bool parse_int( int * const i, const char ** const ibufpp ) { char * tail; errno = 0; const long li = strtol( *ibufpp, &tail, 10 ); if( tail == *ibufpp ) { set_error_msg( "Invalid number" ); return false; } if( errno == ERANGE || li > INT_MAX || li < -INT_MAX ) { set_error_msg( "Number out of range" ); return false; } *ibufpp = tail; *i = li; return true; } /* Get line addresses from the command buffer until an invalid address is seen. Return the number of addresses read, or -1 if error. If no addresses are found, both addresses are set to the current address. If one address is found, both addresses are set to that address. */ static int extract_addresses( const char ** const ibufpp ) { bool first = true; /* true == addr, false == offset */ first_addr = second_addr = -1; /* set to undefined */ skip_blanks( ibufpp ); while( true ) { int n; const unsigned char ch = **ibufpp; if( isdigit( ch ) ) { if( !parse_int( &n, ibufpp ) ) return -1; if( first ) { first = false; second_addr = n; } else second_addr += n; } else switch( ch ) { case '\t': case ' ': ++*ibufpp; skip_blanks( ibufpp ); break; case '+': case '-': if( first ) { first = false; second_addr = current_addr(); } if( isdigit( (unsigned char)(*ibufpp)[1] ) ) { if( !parse_int( &n, ibufpp ) ) return -1; second_addr += n; } else { ++*ibufpp; if( ch == '+' ) ++second_addr; else --second_addr; } break; case '.': case '$': if( !first ) { invalid_address(); return -1; }; first = false; ++*ibufpp; second_addr = ( ( ch == '.' ) ? current_addr() : last_addr() ); break; case '/': case '?': if( !first ) { invalid_address(); return -1; }; second_addr = next_matching_node_addr( ibufpp ); if( second_addr < 0 ) return -1; first = false; break; case '\'':if( !first ) { invalid_address(); return -1; }; first = false; ++*ibufpp; second_addr = get_marked_node_addr( *(*ibufpp)++ ); if( second_addr < 0 ) return -1; break; case '%': case ',': case ';': if( first ) { if( first_addr < 0 ) { first_addr = ( ( ch == ';' ) ? current_addr() : 1 ); second_addr = last_addr(); } else first_addr = second_addr; } else { if( second_addr < 0 || second_addr > last_addr() ) { invalid_address(); return -1; } if( ch == ';' ) set_current_addr( second_addr ); first_addr = second_addr; first = true; } ++*ibufpp; break; default: if( !first && ( second_addr < 0 || second_addr > last_addr() ) ) { invalid_address(); return -1; } { int addr_cnt = 0; /* limited to 2 */ if( second_addr >= 0 ) addr_cnt = ( first_addr >= 0 ) ? 2 : 1; if( addr_cnt <= 0 ) second_addr = current_addr(); if( addr_cnt <= 1 ) first_addr = second_addr; return addr_cnt; } } } } /* get a valid address from the command buffer */ static bool get_third_addr( const char ** const ibufpp, int * const addr ) { const int old1 = first_addr; const int old2 = second_addr; int addr_cnt = extract_addresses( ibufpp ); if( addr_cnt < 0 ) return false; if( traditional() && addr_cnt == 0 ) { set_error_msg( "Destination expected" ); return false; } if( second_addr < 0 || second_addr > last_addr() ) { invalid_address(); return false; } *addr = second_addr; first_addr = old1; second_addr = old2; return true; } /* set default range and return true if address range is valid */ static bool check_addr_range( const int n, const int m, const int addr_cnt ) { if( addr_cnt == 0 ) { first_addr = n; second_addr = m; } if( first_addr < 1 || first_addr > second_addr || second_addr > last_addr() ) { invalid_address(); return false; } return true; } /* set defaults to current_addr and return true if address range is valid */ static bool check_addr_range2( const int addr_cnt ) { return check_addr_range( current_addr(), current_addr(), addr_cnt ); } /* set default second_addr and return true if second_addr is valid */ static bool check_second_addr( const int addr, const int addr_cnt ) { if( addr_cnt == 0 ) second_addr = addr; if( second_addr < 1 || second_addr > last_addr() ) { invalid_address(); return false; } return true; } /* check the command suffixes in the command buffer */ static bool get_command_suffix( const char ** const ibufpp, int * const pflagsp ) { while( true ) { const unsigned char ch = **ibufpp; if( ch == 'l' ) { if( *pflagsp & pf_l ) break; else *pflagsp |= pf_l; } else if( ch == 'n' ) { if( *pflagsp & pf_n ) break; else *pflagsp |= pf_n; } else if( ch == 'p' ) { if( *pflagsp & pf_p ) break; else *pflagsp |= pf_p; } else break; ++*ibufpp; } /* skip newline for global */ if( *(*ibufpp)++ != '\n' ) { set_error_msg( inv_com_suf ); return false; } return true; } /* check the command suffixes for command s in the command buffer */ static bool get_command_s_suffix( const char ** const ibufpp, int * const pflagsp, int * const snump, bool * const ignore_casep ) { bool rep = false; /* repeated g/count */ bool error = false; while( true ) { const unsigned char ch = **ibufpp; if( ch >= '1' && ch <= '9' ) { int n = 0; if( rep || !parse_int( &n, ibufpp ) || n <= 0 ) { error = true; break; } rep = true; *snump = n; continue; } else if( ch == 'g' ) { if( rep ) break; else { rep = true; *snump = 0; } } else if( ch == 'i' || ch == 'I' ) { if( *ignore_casep ) break; else *ignore_casep = true; } else if( ch == 'l' ) { if( *pflagsp & pf_l ) break; else *pflagsp |= pf_l; } else if( ch == 'n' ) { if( *pflagsp & pf_n ) break; else *pflagsp |= pf_n; } else if( ch == 'p' ) { if( *pflagsp & pf_p ) break; else *pflagsp |= pf_p; } else break; ++*ibufpp; } if( error || *(*ibufpp)++ != '\n' ) /* skip newline for global */ { set_error_msg( inv_com_suf ); return false; } return true; } static bool unexpected_address( const int addr_cnt ) { if( addr_cnt > 0 ) { set_error_msg( "Unexpected address" ); return true; } return false; } static bool unexpected_command_suffix( const unsigned char ch ) { if( !isspace( ch ) ) { set_error_msg( "Unexpected command suffix" ); return true; } return false; } static bool command_s( const char ** const ibufpp, int * const pflagsp, const int addr_cnt, const bool isglobal ) { static int pflags = 0; /* print suffixes */ static int pmask = pf_p; /* the print suffixes to be toggled */ static int snum = 1; /* > 0 count, <= 0 global substitute */ enum Sflags { sf_g = 0x01, /* complement previous global substitute suffix */ sf_p = 0x02, /* complement previous print suffix */ sf_r = 0x04, /* use regex of last search (if newer) */ sf_none = 0x08 /* make sflags != 0 if no flags at all */ } sflags = 0; /* if sflags != 0, repeat last substitution */ if( !check_addr_range2( addr_cnt ) ) return false; do { bool error = false; if( **ibufpp >= '1' && **ibufpp <= '9' ) { int n = 0; if( ( sflags & sf_g ) || !parse_int( &n, ibufpp ) || n <= 0 ) error = true; else { sflags |= sf_g; snum = n; } } else switch( **ibufpp ) { case '\n':sflags |= sf_none; break; case 'g': if( sflags & sf_g ) error = true; else { sflags |= sf_g; snum = !snum; ++*ibufpp; } break; case 'p': if( sflags & sf_p ) error = true; else { sflags |= sf_p; ++*ibufpp; } break; case 'r': if( sflags & sf_r ) error = true; else { sflags |= sf_r; ++*ibufpp; } break; default: if( sflags ) error = true; } if( error ) { set_error_msg( inv_com_suf ); return false; } } while( sflags && **ibufpp != '\n' ); if( sflags ) /* repeat last substitution */ { if( !subst_regex() ) { set_error_msg( no_prev_subst ); return false; } if( ( sflags & sf_r ) && !replace_subst_re_by_search_re() ) return false; if( sflags & sf_p ) pflags ^= pmask; } else /* don't compile RE until suffix 'I' is parsed */ { const char * pat = get_pattern_for_s( ibufpp ); if( !pat ) return false; const char delimiter = **ibufpp; if( !extract_replacement( ibufpp, isglobal ) ) return false; pflags = 0; snum = 1; bool ignore_case = false; if( **ibufpp == '\n' ) /* omitted last delimiter */ { ++*ibufpp; pflags = pf_p; } /* skip newline for global */ else { if( **ibufpp == delimiter ) ++*ibufpp; /* skip delimiter */ if( !get_command_s_suffix( ibufpp, &pflags, &snum, &ignore_case ) ) return false; } pmask = pflags & ( pf_l | pf_n | pf_p ); if( pmask == 0 ) pmask = pf_p; if( !set_subst_regex( pat, ignore_case ) ) return false; } *pflagsp = pflags; if( !isglobal ) clear_undo_stack(); if( !search_and_replace( first_addr, second_addr, snum, isglobal ) ) return false; return true; } static int exec_global( const char ** const ibufpp, const int pflags, const bool interactive ); /* execute the next command in command buffer; return error status */ static int exec_command( const char ** const ibufpp, const bool isglobal ) { const char * fnp; /* filename */ int pflags = 0; /* print suffixes */ int addr, c, n; const int addr_cnt = extract_addresses( ibufpp ); if( addr_cnt < 0 ) return ERR; skip_blanks( ibufpp ); c = *(*ibufpp)++; switch( c ) { case 'a': if( !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !append_lines( ibufpp, second_addr, false, isglobal ) ) return ERR; break; case 'c': if( !check_addr_range2( addr_cnt ) || !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !delete_lines( first_addr, second_addr, isglobal ) || !append_lines( ibufpp, current_addr(), current_addr() >= first_addr, isglobal ) ) return ERR; break; case 'd': if( !check_addr_range2( addr_cnt ) || !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !delete_lines( first_addr, second_addr, isglobal ) ) return ERR; break; case 'e': if( modified() && !warned() ) return EMOD; /* fall through */ case 'E': if( unexpected_address( addr_cnt ) || unexpected_command_suffix( **ibufpp ) ) return ERR; fnp = get_filename( ibufpp, false ); if( !fnp || !delete_lines( 1, last_addr(), isglobal ) || !close_sbuf() ) return ERR; set_modified( false ); /* buffer is now empty */ if( !open_sbuf() ) return FATAL; if( fnp[0] && fnp[0] != '!' && !set_def_filename( fnp ) ) return ERR; if( read_file( fnp[0] ? fnp : def_filename, 0, &read_only ) < 0 ) return ERR; reset_undo_state(); /* to prevent undoing the read */ break; case 'f': if( unexpected_address( addr_cnt ) || unexpected_command_suffix( **ibufpp ) ) return ERR; fnp = get_filename( ibufpp, traditional() ); if( !fnp ) return ERR; if( fnp[0] == '!' ) { set_error_msg( "Invalid redirection" ); return ERR; } if( fnp[0] && !set_def_filename( fnp ) ) return ERR; print_filename( def_filename, true ); putchar('\n'); break; case 'g': case 'v': case 'G': case 'V': if( isglobal ) { set_error_msg( "Cannot nest global commands" ); return ERR; } n = ( c == 'g' || c == 'G' ); /* mark matching lines */ if( !check_addr_range( 1, last_addr(), addr_cnt ) || !build_active_list( ibufpp, first_addr, second_addr, n ) ) return ERR; n = ( c == 'G' || c == 'V' ); /* interactive */ if( n && !get_command_suffix( ibufpp, &pflags ) ) return ERR; n = exec_global( ibufpp, pflags, n ); if( n != 0 ) return n; break; case 'h': case 'H': if( unexpected_address( addr_cnt ) || !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( c == 'H' ) verbose = !verbose; if( ( c == 'h' || verbose ) && errmsg[0] ) printf( "%s\n", errmsg ); break; case 'i': if( !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !append_lines( ibufpp, second_addr, true, isglobal ) ) return ERR; break; case 'j': if( !check_addr_range( current_addr(), current_addr() + 1, addr_cnt ) || !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( first_addr < second_addr && !join_lines( first_addr, second_addr, isglobal ) ) return ERR; break; case 'k': n = *(*ibufpp)++; if( second_addr == 0 ) { invalid_address(); return ERR; } if( !get_command_suffix( ibufpp, &pflags ) || !mark_line_node( search_line_node( second_addr ), n ) ) return ERR; break; case 'l': n = pf_l; goto pflabel; case 'n': n = pf_n; goto pflabel; case 'p': n = pf_p; pflabel: if( !check_addr_range2( addr_cnt ) || !get_command_suffix( ibufpp, &pflags ) || !print_lines( first_addr, second_addr, pflags | n ) ) return ERR; pflags = 0; break; case 'm': if( !check_addr_range2( addr_cnt ) || !get_third_addr( ibufpp, &addr ) ) return ERR; if( addr >= first_addr && addr < second_addr ) { set_error_msg( "Invalid destination" ); return ERR; } if( !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !move_lines( first_addr, second_addr, addr, isglobal ) ) return ERR; break; case 'P': case 'q': case 'Q': if( unexpected_address( addr_cnt ) || !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( c == 'P' ) { prompt_on = !prompt_on; break; } return ( c == 'q' && modified() && !warned() ) ? EMOD : QUIT; case 'r': if( unexpected_command_suffix( **ibufpp ) ) return ERR; if( addr_cnt == 0 ) second_addr = last_addr(); fnp = get_filename( ibufpp, false ); if( !fnp ) return ERR; if( !def_filename[0] && fnp[0] != '!' && !set_def_filename( fnp ) ) return ERR; if( !isglobal ) clear_undo_stack(); addr = read_file( fnp[0] ? fnp : def_filename, second_addr, 0 ); if( addr < 0 ) return ERR; if( addr ) set_modified( true ); break; case 's': if( !command_s( ibufpp, &pflags, addr_cnt, isglobal ) ) return ERR; break; case 't': if( !check_addr_range2( addr_cnt ) || !get_third_addr( ibufpp, &addr ) || !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !copy_lines( first_addr, second_addr, addr ) ) return ERR; break; case 'u': if( unexpected_address( addr_cnt ) || !get_command_suffix( ibufpp, &pflags ) || !undo( isglobal ) ) return ERR; break; case 'w': case 'W': n = **ibufpp; if( n == 'q' || n == 'Q' ) ++*ibufpp; if( unexpected_command_suffix( **ibufpp ) ) return ERR; fnp = get_filename( ibufpp, false ); if( !fnp ) return ERR; if( addr_cnt == 0 && last_addr() == 0 ) first_addr = second_addr = 0; else if( !check_addr_range( 1, last_addr(), addr_cnt ) ) return ERR; if( !def_filename[0] && fnp[0] != '!' && !set_def_filename( fnp ) ) return ERR; addr = write_file( fnp[0] ? fnp : def_filename, ( c == 'W' ) ? "a" : "w", first_addr, second_addr ); if( addr < 0 ) return ERR; if( addr == last_addr() && fnp[0] != '!' ) set_modified( false ); else if( n == 'q' && modified() && !warned() ) return EMOD; if( n == 'q' || n == 'Q' ) return QUIT; /* wq */ break; case 'x': if( second_addr < 0 || second_addr > last_addr() ) { invalid_address(); return ERR; } if( !get_command_suffix( ibufpp, &pflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !put_lines( second_addr ) ) return ERR; break; case 'y': if( !check_addr_range2( addr_cnt ) || !get_command_suffix( ibufpp, &pflags ) || !yank_lines( first_addr, second_addr ) ) return ERR; break; case 'z': if( !check_second_addr( current_addr() + !isglobal, addr_cnt ) ) return ERR; if( **ibufpp > '0' && **ibufpp <= '9' ) { if( parse_int( &n, ibufpp ) ) set_window_lines( n ); else return ERR; } if( !get_command_suffix( ibufpp, &pflags ) || !print_lines( second_addr, min( last_addr(), second_addr + window_lines() - 1 ), pflags ) ) return ERR; pflags = 0; break; case '=': if( !get_command_suffix( ibufpp, &pflags ) ) return ERR; printf( "%d\n", addr_cnt ? second_addr : last_addr() ); break; case '!': if( unexpected_address( addr_cnt ) ) return ERR; fnp = get_shell_command( ibufpp ); if( !fnp ) return ERR; if( system( fnp + 1 ) < 0 ) { set_error_msg( "Can't create shell process" ); return ERR; } if( !scripted() ) fputs( "!\n", stdout ); break; case '\n': if( !check_second_addr( current_addr() + ( traditional() || !isglobal ), addr_cnt ) || !print_lines( second_addr, second_addr, 0 ) ) return ERR; break; case '#': while( *(*ibufpp)++ != '\n' ) {} /* skip newline for global */ break; default: set_error_msg( "Unknown command" ); return ERR; } if( pflags && !print_lines( current_addr(), current_addr(), pflags ) ) return ERR; return 0; } /* Apply command list in the command buffer to the active lines in a range. Stop at first error. Return status of last command executed. */ static int exec_global( const char ** const ibufpp, const int pflags, const bool interactive ) { static char * buf = 0; static int bufsz = 0; const char * cmd = 0; if( !interactive ) { if( traditional() && strcmp( *ibufpp, "\n" ) == 0 ) cmd = "p\n"; /* null cmd_list == 'p' */ else { if( !get_extended_line( ibufpp, 0, false ) ) return ERR; cmd = *ibufpp; } } clear_undo_stack(); while( true ) { const line_node * const lp = next_active_node(); if( !lp ) break; set_current_addr( get_line_node_addr( lp ) ); if( current_addr() < 0 ) return ERR; if( interactive ) { /* print current_addr; get a command in global syntax */ int len = 0; if( !print_lines( current_addr(), current_addr(), pflags ) ) return ERR; *ibufpp = get_stdin_line( &len ); if( !*ibufpp ) return ERR; /* error */ if( len <= 0 ) return ERR; /* EOF */ if( len == 1 && strcmp( *ibufpp, "\n" ) == 0 ) continue; if( len == 2 && strcmp( *ibufpp, "&\n" ) == 0 ) { if( !cmd ) { set_error_msg( no_prev_com ); return ERR; } } else { if( !get_extended_line( ibufpp, &len, false ) || !resize_buffer( &buf, &bufsz, len + 1 ) ) return ERR; memcpy( buf, *ibufpp, len + 1 ); cmd = buf; } } *ibufpp = cmd; while( **ibufpp ) { const int status = exec_command( ibufpp, true ); if( status != 0 ) return status; } } return 0; } int main_loop( const bool initial_error, const bool loose ) { extern jmp_buf jmp_state; const char * ibufp; /* pointer to command buffer */ volatile int err_status = 0; /* program exit status */ int len = 0, status; disable_interrupts(); set_signals(); status = setjmp( jmp_state ); if( status == 0 ) /* direct invocation of setjmp */ { enable_interrupts(); if( initial_error ) status = err_status = 1; } else { status = -1; fputs( "\n?\n", stdout ); set_error_msg( "Interrupt" ); } while( true ) { fflush( stdout ); fflush( stderr ); if( status < 0 && verbose ) { printf( "%s\n", errmsg ); fflush( stdout ); } if( prompt_on ) { fputs( prompt_str, stdout ); fflush( stdout ); } ibufp = get_stdin_line( &len ); if( !ibufp ) return 2; /* an error happened */ if( len <= 0 ) /* EOF on stdin ('q') */ { if( !modified() || status == EMOD ) status = QUIT; else { status = EMOD; if( !loose ) err_status = 2; } } else status = exec_command( &ibufp, false ); if( status == 0 ) { if( read_only && modified() ) { read_only = false; show_warning( def_filename, "warning: read-only file" ); } continue; } if( status == QUIT ) return err_status; fputs( "?\n", stdout ); /* give warning */ if( !loose && err_status == 0 ) err_status = 1; set_warned( status == EMOD ); /* errors reset warned */ if( warned() ) set_error_msg( "Warning: buffer modified" ); if( !interactive() ) { if( verbose ) printf( "script, line %d: %s\n", linenum(), errmsg ); return ( status == FATAL ) ? 1 : err_status; } if( status == FATAL ) { if( verbose ) { printf( "%s\n", errmsg ); } return 1; } } } ed_1.21.1/signal.c0000644000175000017500000001312714770104457013536 0ustar dogslegdogsleg/* signal.c: signal and miscellaneous routines for the ed line editor. */ /* GNU ed - The GNU line editor. Copyright (C) 1993, 1994 Andrew L. Moore, Talke Studio Copyright (C) 2006-2025 Antonio Diaz Diaz. 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "ed.h" jmp_buf jmp_state; /* jumps to main_loop */ static int mutex = 0; /* if > 0, signals stay pending */ static int user_lines = -1; /* LINES or argument of z command */ /* if > 0, overrides window_lines_ */ static int window_lines_ = 22; /* scroll lines set by sigwinch_handler */ static int window_columns_ = 76; static bool sighup_pending = false; static bool sigint_pending = false; const char * home_directory( void ) { static bool first_time = true; static char * buf = 0; static int bufsz = 0; if( first_time ) { first_time = false; const char * const hd = getenv( "HOME" ); if( !hd || !hd[0] ) return 0; const int hdsize = strlen( hd ); if( !resize_buffer( &buf, &bufsz, hdsize + 1 ) ) return 0; memcpy( buf, hd, hdsize ); buf[hdsize] = 0; } return buf; } static void sighup_handler( int signum ) { if( signum ) {} /* keep compiler happy */ if( mutex ) { sighup_pending = true; return; } sighup_pending = false; const char hb[] = "ed.hup"; if( last_addr() <= 0 || !modified() || write_file( hb, "w", 1, last_addr() ) >= 0 ) exit( 0 ); const char * const hd = home_directory(); if( !hd || !hd[0] ) exit( 1 ); const int hdsize = strlen( hd ); const int need_slash = hd[hdsize-1] != '/'; char * const hup = ( hdsize + need_slash + (int)sizeof hb < path_max( 0 ) ) ? (char *)malloc( hdsize + need_slash + sizeof hb ) : 0; if( !hup ) exit( 1 ); /* hup file name */ memcpy( hup, hd, hdsize ); if( need_slash ) hup[hdsize] = '/'; memcpy( hup + hdsize + need_slash, hb, sizeof hb ); if( write_file( hup, "w", 1, last_addr() ) >= 0 ) exit( 0 ); exit( 1 ); /* hup file write failed */ } static void sigint_handler( int signum ) { if( mutex ) sigint_pending = true; else { sigset_t set; sigint_pending = false; sigemptyset( &set ); sigaddset( &set, signum ); sigprocmask( SIG_UNBLOCK, &set, 0 ); longjmp( jmp_state, -1 ); } } static void sigwinch_handler( int signum ) { #ifdef TIOCGWINSZ struct winsize ws; /* window size structure */ if( ioctl( 0, TIOCGWINSZ, (char *) &ws ) >= 0 ) { /* Sanity check values of environment vars */ if( ws.ws_row > 2 && ws.ws_row < 600 ) window_lines_ = ws.ws_row - 2; if( ws.ws_col > 8 && ws.ws_col < 1800 ) window_columns_ = ws.ws_col - 4; } #endif if( signum ) {} /* keep compiler happy */ } static int set_signal( const int signum, void (*handler)( int ) ) { struct sigaction new_action; new_action.sa_handler = handler; sigemptyset( &new_action.sa_mask ); #ifdef SA_RESTART new_action.sa_flags = SA_RESTART; #else new_action.sa_flags = 0; #endif return sigaction( signum, &new_action, 0 ); } void enable_interrupts( void ) { if( --mutex <= 0 ) { mutex = 0; if( sighup_pending ) sighup_handler( SIGHUP ); if( sigint_pending ) sigint_handler( SIGINT ); } } void disable_interrupts( void ) { ++mutex; } void set_signals( void ) { #ifdef SIGWINCH sigwinch_handler( SIGWINCH ); if( isatty( 0 ) ) set_signal( SIGWINCH, sigwinch_handler ); #endif set_signal( SIGHUP, sighup_handler ); set_signal( SIGQUIT, SIG_IGN ); set_signal( SIGINT, sigint_handler ); } void set_window_lines( const int lines ) { user_lines = lines; } int window_columns( void ) { return window_columns_; } int window_lines( void ) { if( user_lines < 0 ) /* set initial size */ { const char * const p = getenv( "LINES" ); if( p && p[0] ) { char * tail; errno = 0; const long n = strtol( p, &tail, 10 ); if( errno == 0 && tail != p && n > 0 && n <= INT_MAX ) user_lines = n; } if( user_lines < 0 ) user_lines = 0; /* LINES not found or invalid */ } return ( user_lines > 0 ) ? user_lines : window_lines_; } /* assure at least a minimum size for buffer 'buf' up to INT_MAX - 1 */ bool resize_buffer( char ** const buf, int * const size, const unsigned min_size ) { if( (unsigned)*size < min_size ) { if( min_size >= INT_MAX ) { set_error_msg( "Line too long" ); return false; } const int new_size = ( ( min_size < 512 ) ? 512 : ( min_size >= INT_MAX / 2 ) ? INT_MAX - 1 : ( min_size / 512 ) * 1024 ); void * new_buf = 0; disable_interrupts(); if( *buf ) new_buf = realloc( *buf, new_size ); else new_buf = malloc( new_size ); if( !new_buf ) { show_strerror( 0, errno ); set_error_msg( mem_msg ); enable_interrupts(); return false; } *size = new_size; *buf = (char *)new_buf; enable_interrupts(); } return true; } ed_1.21.1/red.in0000644000175000017500000000013111373234313013175 0ustar dogslegdogsleg#! /bin/sh bindir=`echo "$0" | sed -e 's,[^/]*$,,'` exec "${bindir}"ed --restricted "$@" ed_1.21.1/Makefile.in0000644000175000017500000001162214711745263014161 0ustar dogslegdogsleg DISTNAME = $(pkgname)-$(pkgversion) INSTALL = install INSTALL_PROGRAM = $(INSTALL) -m 755 INSTALL_SCRIPT = $(INSTALL) -m 755 INSTALL_DIR = $(INSTALL) -d -m 755 INSTALL_DATA = $(INSTALL) -m 644 SHELL = /bin/sh CAN_RUN_INSTALLINFO = $(SHELL) -c "install-info --version" > /dev/null 2>&1 objs = buffer.o carg_parser.o global.o io.o main.o main_loop.o regex.o signal.o .PHONY : all install install-bin install-info install-man-base install-man \ install-strip install-compress install-strip-compress \ install-bin-strip install-info-compress install-man-compress \ uninstall uninstall-bin uninstall-info uninstall-man \ doc info man check dist clean distclean all : $(progname) r$(progname) $(progname) : $(objs) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(objs) r$(progname) : r$(progname).in cat $(VPATH)/r$(progname).in > $@ chmod a+x $@ main.o : main.c $(CC) $(CPPFLAGS) $(CFLAGS) -DPROGVERSION=\"$(pkgversion)\" -c -o $@ $< %.o : %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< # prevent 'make' from trying to remake source files $(VPATH)/configure $(VPATH)/Makefile.in $(VPATH)/doc/$(pkgname).texi : ; MAKEFLAGS += -r .SUFFIXES : $(objs) : Makefile ed.h carg_parser.o : carg_parser.h main.o : carg_parser.h doc : info man info : $(VPATH)/doc/$(pkgname).info $(VPATH)/doc/$(pkgname).info : $(VPATH)/doc/$(pkgname).texi cd $(VPATH)/doc && $(MAKEINFO) $(pkgname).texi man : $(VPATH)/doc/$(progname).1 $(VPATH)/doc/$(progname).1 : $(progname) help2man -n 'line-oriented text editor' -o $@ ./$(progname) Makefile : $(VPATH)/configure $(VPATH)/Makefile.in ./config.status check : all @$(VPATH)/testsuite/check.sh $(VPATH)/testsuite $(pkgversion) install : install-bin install-info install-man install-strip : install-bin-strip install-info install-man install-compress : install-bin install-info-compress install-man-compress install-strip-compress : install-bin-strip install-info-compress install-man-compress install-bin : all if [ ! -d "$(DESTDIR)$(bindir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(bindir)" ; fi $(INSTALL_PROGRAM) ./$(progname) "$(DESTDIR)$(bindir)/$(program_prefix)$(progname)" $(INSTALL_SCRIPT) ./r$(progname) "$(DESTDIR)$(bindir)/$(program_prefix)r$(progname)" install-bin-strip : all $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install-bin install-info : if [ ! -d "$(DESTDIR)$(infodir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(infodir)" ; fi -rm -f "$(DESTDIR)$(infodir)/$(pkgname).info"* $(INSTALL_DATA) $(VPATH)/doc/$(pkgname).info "$(DESTDIR)$(infodir)/$(program_prefix)$(pkgname).info" -if $(CAN_RUN_INSTALLINFO) ; then \ install-info --info-dir="$(DESTDIR)$(infodir)" "$(DESTDIR)$(infodir)/$(program_prefix)$(pkgname).info" ; \ fi install-info-compress : install-info lzip -v -9 "$(DESTDIR)$(infodir)/$(pkgname).info" install-man-base : if [ ! -d "$(DESTDIR)$(mandir)/man1" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1" ; fi -rm -f "$(DESTDIR)$(mandir)/man1/$(program_prefix)$(progname).1"* -rm -f "$(DESTDIR)$(mandir)/man1/$(program_prefix)r$(progname).1"* $(INSTALL_DATA) $(VPATH)/doc/$(progname).1 "$(DESTDIR)$(mandir)/man1/$(program_prefix)$(progname).1" install-man : install-man-base cd "$(DESTDIR)$(mandir)/man1" && ln -s "$(program_prefix)$(progname).1" "$(program_prefix)r$(progname).1" install-man-compress : install-man-base lzip -v -9 "$(DESTDIR)$(mandir)/man1/$(program_prefix)$(progname).1" cd "$(DESTDIR)$(mandir)/man1" && ln -s "$(program_prefix)$(progname).1.lz" "$(program_prefix)r$(progname).1.lz" uninstall : uninstall-man uninstall-info uninstall-bin uninstall-bin : -rm -f "$(DESTDIR)$(bindir)/$(program_prefix)$(progname)" -rm -f "$(DESTDIR)$(bindir)/$(program_prefix)r$(progname)" uninstall-info : -if $(CAN_RUN_INSTALLINFO) ; then \ install-info --info-dir="$(DESTDIR)$(infodir)" --remove "$(DESTDIR)$(infodir)/$(program_prefix)$(pkgname).info" ; \ fi -rm -f "$(DESTDIR)$(infodir)/$(program_prefix)$(pkgname).info"* uninstall-man : -rm -f "$(DESTDIR)$(mandir)/man1/$(program_prefix)$(progname).1"* -rm -f "$(DESTDIR)$(mandir)/man1/$(program_prefix)r$(progname).1"* dist : doc ln -sf $(VPATH) $(DISTNAME) tar -Hustar --owner=root --group=root -cvf $(DISTNAME).tar \ $(DISTNAME)/AUTHORS \ $(DISTNAME)/COPYING \ $(DISTNAME)/ChangeLog \ $(DISTNAME)/INSTALL \ $(DISTNAME)/Makefile.in \ $(DISTNAME)/NEWS \ $(DISTNAME)/README \ $(DISTNAME)/configure \ $(DISTNAME)/doc/$(progname).1 \ $(DISTNAME)/doc/$(pkgname).info \ $(DISTNAME)/doc/$(pkgname).texi \ $(DISTNAME)/doc/fdl.texi \ $(DISTNAME)/r$(progname).in \ $(DISTNAME)/*.h \ $(DISTNAME)/*.c \ $(DISTNAME)/testsuite/check.sh \ $(DISTNAME)/testsuite/test.bin \ $(DISTNAME)/testsuite/test.txt \ $(DISTNAME)/testsuite/*.ed \ $(DISTNAME)/testsuite/*.r \ $(DISTNAME)/testsuite/*.err rm -f $(DISTNAME) lzip -v -9 $(DISTNAME).tar clean : -rm -f $(progname) r$(progname) $(objs) distclean : clean -rm -f Makefile config.status *.tar *.tar.lz ed_1.21.1/buffer.c0000644000175000017500000004352014767603470013537 0ustar dogslegdogsleg/* buffer.c: scratch-file buffer routines for the ed line editor. */ /* GNU ed - The GNU line editor. Copyright (C) 1993, 1994 Andrew L. Moore, Talke Studio Copyright (C) 2006-2025 Antonio Diaz Diaz. 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "ed.h" static int current_addr_ = 0; /* current address in editor buffer */ static int last_addr_ = 0; /* last address in editor buffer */ static bool isbinary_ = false; /* buffer contains ASCII NULs */ static unsigned char modified_ = false; /* 1=modified | 2=warned */ static bool seek_write = false; /* seek before writing */ static FILE * sfp = 0; /* scratch file pointer */ static long sfpos = 0; /* scratch file position */ static line_node buffer_head; /* editor buffer (linked list of line_node) */ static line_node yank_buffer_head; int current_addr( void ) { return current_addr_; } int inc_current_addr( void ) { if( ++current_addr_ > last_addr_ ) current_addr_ = last_addr_; return current_addr_; } void set_current_addr( const int addr ) { current_addr_ = addr; } int last_addr( void ) { return last_addr_; } bool isbinary( void ) { return isbinary_; } void set_binary( void ) { isbinary_ = true; } bool modified( void ) { return modified_ & 1; } /* ignore warned */ void set_modified( const bool b ) { modified_ = b != 0; } /* clear warned */ bool warned( void ) { return modified_ == 3; } void set_warned( const bool b ) { if( b ) modified_ |= 2; else modified_ &= 1; } /* line 0 does not contain text, but it is part of the linked list of line_node. Therefore inc_addr and dec_addr must cycle through addr 0. */ int inc_addr( int addr ) { if( ++addr > last_addr_ ) addr = 0; return addr; } int dec_addr( int addr ) { if( --addr < 0 ) addr = last_addr_; return addr; } /* link next and previous nodes */ static void link_nodes( line_node * const prev, line_node * const next ) { prev->q_forw = next; next->q_back = prev; } /* insert line node into circular queue after previous */ static void insert_node( line_node * const lp, line_node * const prev ) { link_nodes( lp, prev->q_forw ); link_nodes( prev, lp ); } /* to be called before add_line_node */ static bool too_many_lines( void ) { if( last_addr_ < INT_MAX - 2 ) return false; set_error_msg( "Too many lines in buffer" ); return true; } /* add a line node in the editor buffer after the given line */ static void add_line_node( line_node * const lp ) { line_node * const prev = search_line_node( current_addr_ ); insert_node( lp, prev ); ++current_addr_; ++last_addr_; } /* return a pointer to a copy of a line node, or to a new node if lp == 0 */ static line_node * dup_line_node( line_node * const lp ) { line_node * const p = (line_node *) malloc( sizeof (line_node) ); if( !p ) { show_strerror( 0, errno ); set_error_msg( mem_msg ); return 0; } if( lp ) { p->pos = lp->pos; p->len = lp->len; } return p; } /* Insert text from stdin (or from command buffer if global) to after line n; stop when either a single period is read or at EOF. Return false if insertion fails. */ bool append_lines( const char ** const ibufpp, const int addr, bool insert, const bool isglobal ) { int size = 0; undo_atom * up = 0; current_addr_ = addr; while( true ) { if( !isglobal ) { *ibufpp = get_stdin_line( &size ); if( !*ibufpp ) return false; /* error */ if( size <= 0 ) return true; /* EOF */ } else { if( !**ibufpp ) return true; for( size = 0; (*ibufpp)[size++] != '\n'; ) ; } if( size == 2 && **ibufpp == '.' ) { *ibufpp += size; return true; } disable_interrupts(); if( insert ) { insert = false; if( current_addr_ > 0 ) --current_addr_; } if( !put_sbuf_line( *ibufpp, size ) ) { enable_interrupts(); return false; } if( up ) up->tail = search_line_node( current_addr_ ); else { up = push_undo_atom( UADD, current_addr_, current_addr_ ); if( !up ) { enable_interrupts(); return false; } } *ibufpp += size; modified_ = true; enable_interrupts(); } } static void clear_yank_buffer( void ) { line_node * lp = yank_buffer_head.q_forw; disable_interrupts(); while( lp != &yank_buffer_head ) { line_node * const p = lp->q_forw; link_nodes( lp->q_back, lp->q_forw ); free( lp ); lp = p; } enable_interrupts(); } /* close scratch file */ bool close_sbuf( void ) { clear_yank_buffer(); clear_undo_stack(); if( sfp ) { if( fclose( sfp ) != 0 ) { show_strerror( 0, errno ); set_error_msg( "Cannot close temp file" ); return false; } sfp = 0; } sfpos = 0; seek_write = false; return true; } /* copy a range of lines; return false if error */ bool copy_lines( const int first_addr, const int second_addr, const int addr ) { line_node * np = search_line_node( first_addr ); undo_atom * up = 0; int n = second_addr - first_addr + 1; int m = 0; current_addr_ = addr; if( addr >= first_addr && addr < second_addr ) { n = addr - first_addr + 1; m = second_addr - addr; } for( ; n > 0; n = m, m = 0, np = search_line_node( current_addr_ + 1 ) ) for( ; n-- > 0; np = np->q_forw ) { if( too_many_lines() ) return false; disable_interrupts(); line_node * lp = dup_line_node( np ); if( !lp ) { enable_interrupts(); return false; } add_line_node( lp ); if( up ) up->tail = lp; else { up = push_undo_atom( UADD, current_addr_, current_addr_ ); if( !up ) { enable_interrupts(); return false; } } modified_ = true; enable_interrupts(); } return true; } /* delete a range of lines */ bool delete_lines( const int from, const int to, const bool isglobal ) { if( !yank_lines( from, to ) ) return false; disable_interrupts(); if( !push_undo_atom( UDEL, from, to ) ) { enable_interrupts(); return false; } line_node * n = search_line_node( inc_addr( to ) ); line_node * p = search_line_node( from - 1 ); /* this search_line_node last! */ if( isglobal ) unset_active_nodes( p->q_forw, n ); link_nodes( p, n ); last_addr_ -= to - from + 1; current_addr_ = min( from, last_addr_ ); modified_ = true; enable_interrupts(); return true; } /* return line number of pointer */ int get_line_node_addr( const line_node * const lp ) { const line_node * p = &buffer_head; int addr = 0; while( p != lp && ( p = p->q_forw ) != &buffer_head ) ++addr; if( addr && p == &buffer_head ) { invalid_address(); return -1; } return addr; } /* get a line of text from the scratch file; return pointer to the text */ char * get_sbuf_line( const line_node * const lp ) { static char * buf = 0; static int bufsz = 0; int len; if( lp == &buffer_head ) return 0; seek_write = true; /* force seek on write */ /* out of position */ if( sfpos != lp->pos ) { sfpos = lp->pos; if( fseek( sfp, sfpos, SEEK_SET ) != 0 ) { show_strerror( 0, errno ); set_error_msg( "Cannot seek temp file" ); return 0; } } len = lp->len; if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return 0; if( (int)fread( buf, 1, len, sfp ) != len ) { show_strerror( 0, errno ); set_error_msg( "Cannot read temp file" ); return 0; } sfpos += len; /* update file position */ buf[len] = 0; return buf; } /* open scratch buffer; initialize line queue */ bool init_buffers( void ) { /* Read stdin one character at a time to avoid i/o contention with shell escapes invoked by nonterminal input, e.g., ed - <len ) ) return false; memcpy( buf + size, s, bp->len ); size += bp->len; bp = bp->q_forw; } if( !resize_buffer( &buf, &bufsz, size + 2 ) ) return false; buf[size++] = '\n'; buf[size++] = 0; if( !delete_lines( from, to, isglobal ) ) return false; current_addr_ = from - 1; disable_interrupts(); if( !put_sbuf_line( buf, size ) || !push_undo_atom( UADD, current_addr_, current_addr_ ) ) { enable_interrupts(); return false; } modified_ = true; enable_interrupts(); return true; } /* move a range of lines */ bool move_lines( const int first_addr, const int second_addr, const int addr, const bool isglobal ) { line_node *b1, *a1, *b2, *a2; int n = inc_addr( second_addr ); int p = first_addr - 1; disable_interrupts(); if( addr == first_addr - 1 || addr == second_addr ) { a2 = search_line_node( n ); b2 = search_line_node( p ); current_addr_ = second_addr; } else if( !push_undo_atom( UMOV, p, n ) || !push_undo_atom( UMOV, addr, inc_addr( addr ) ) ) { enable_interrupts(); return false; } else { a1 = search_line_node( n ); if( addr < first_addr ) { b1 = search_line_node( p ); b2 = search_line_node( addr ); /* this search_line_node last! */ } else { b2 = search_line_node( addr ); b1 = search_line_node( p ); /* this search_line_node last! */ } a2 = b2->q_forw; link_nodes( b2, b1->q_forw ); link_nodes( a1->q_back, a2 ); link_nodes( b1, a1 ); current_addr_ = addr + ( ( addr < first_addr ) ? second_addr - first_addr + 1 : 0 ); } if( isglobal ) unset_active_nodes( b2->q_forw, a2 ); modified_ = true; enable_interrupts(); return true; } /* open scratch file */ bool open_sbuf( void ) { isbinary_ = false; reset_unterminated_line(); sfp = tmpfile(); if( !sfp ) { show_strerror( 0, errno ); set_error_msg( "Cannot open temp file" ); return false; } return true; } int path_max( const char * filename ) { if( !filename ) filename = "/"; errno = 0; long result = pathconf( filename, _PC_PATH_MAX ); if( result < 0 ) { if( errno ) result = 256; else result = 1024; } else if( result < 256 ) result = 256; return result; } /* append lines from the yank buffer */ bool put_lines( const int addr ) { undo_atom * up = 0; line_node * lp = yank_buffer_head.q_forw; if( lp == &yank_buffer_head ) { set_error_msg( "Nothing to put" ); return false; } current_addr_ = addr; while( lp != &yank_buffer_head ) { if( too_many_lines() ) return false; disable_interrupts(); line_node * p = dup_line_node( lp ); if( !p ) { enable_interrupts(); return false; } add_line_node( p ); if( up ) up->tail = p; else { up = push_undo_atom( UADD, current_addr_, current_addr_ ); if( !up ) { enable_interrupts(); return false; } } modified_ = true; lp = lp->q_forw; enable_interrupts(); } return true; } /* Write a line of text to the scratch file and add a line node to the editor buffer. The text line stops at the first newline and may be shorter than size. Return a pointer to the char following the newline in buf, or 0 if error. */ const char * put_sbuf_line( const char * const buf, const int size ) { const char * const p = (const char *) memchr( buf, '\n', size ); if( !p ) { set_error_msg( "internal error: unterminated line passed to put_sbuf_line" ); return 0; } const int len = p - buf; if( too_many_lines() ) return 0; if( seek_write ) /* out of position */ { if( fseek( sfp, 0L, SEEK_END ) != 0 ) { show_strerror( 0, errno ); set_error_msg( "Cannot seek temp file" ); return 0; } sfpos = ftell( sfp ); seek_write = false; } if( (int)fwrite( buf, 1, len, sfp ) != len ) /* assert: interrupts disabled */ { sfpos = -1; show_strerror( 0, errno ); set_error_msg( "Cannot write temp file" ); return 0; } line_node * lp = dup_line_node( 0 ); if( !lp ) return 0; lp->pos = sfpos; lp->len = len; add_line_node( lp ); sfpos += len; /* update file position */ return p + 1; } /* return pointer to a line node in the editor buffer */ line_node * search_line_node( const int addr ) { static line_node * lp = &buffer_head; static int o_addr = 0; disable_interrupts(); if( o_addr < addr ) { if( o_addr + last_addr_ >= 2 * addr ) while( o_addr < addr ) { ++o_addr; lp = lp->q_forw; } else { lp = buffer_head.q_back; o_addr = last_addr_; while( o_addr > addr ) { --o_addr; lp = lp->q_back; } } } else if( o_addr <= 2 * addr ) while( o_addr > addr ) { --o_addr; lp = lp->q_back; } else { lp = &buffer_head; o_addr = 0; while( o_addr < addr ) { ++o_addr; lp = lp->q_forw; } } enable_interrupts(); return lp; } /* copy a range of lines to the cut buffer */ bool yank_lines( const int from, const int to ) { line_node * const ep = search_line_node( inc_addr( to ) ); line_node * bp = search_line_node( from ); line_node * lp = &yank_buffer_head; clear_yank_buffer(); while( bp != ep ) { disable_interrupts(); line_node * p = dup_line_node( bp ); if( !p ) { enable_interrupts(); return false; } insert_node( p, lp ); bp = bp->q_forw; lp = p; enable_interrupts(); } return true; } static undo_atom * ustack = 0; /* undo stack */ static int usize = 0; /* ustack size (in bytes) */ static int u_len = 0; /* undo stack size (in atoms) */ static int u_current_addr = -1; /* if < 0, undo disabled */ static int u_last_addr = -1; /* if < 0, undo disabled */ static bool u_modified = false; void clear_undo_stack( void ) { while( u_len-- ) if( ustack[u_len].type == UDEL ) { line_node * const ep = ustack[u_len].tail->q_forw; line_node * bp = ustack[u_len].head; while( bp != ep ) { line_node * const lp = bp->q_forw; unmark_line_node( bp ); unmark_unterminated_line( bp ); free( bp ); bp = lp; } } u_len = 0; u_current_addr = current_addr_; u_last_addr = last_addr_; u_modified = modified(); } void reset_undo_state( void ) { clear_undo_stack(); u_current_addr = u_last_addr = -1; u_modified = false; } static void free_undo_stack( void ) { if( ustack ) { clear_undo_stack(); free( ustack ); ustack = 0; usize = u_len = 0; u_current_addr = u_last_addr = -1; } } /* return pointer to intialized undo atom */ undo_atom * push_undo_atom( const int type, const int from, const int to ) { const unsigned min_size = ( u_len + 1 ) * sizeof (undo_atom); disable_interrupts(); if( (unsigned)usize < min_size ) { if( min_size >= INT_MAX ) { set_error_msg( "Undo stack too long" ); free_undo_stack(); enable_interrupts(); return 0; } const int new_size = ( ( min_size < 512 ) ? 512 : ( min_size >= INT_MAX / 2 ) ? INT_MAX - 1 : ( min_size / 512 ) * 1024 ); void * new_buf = 0; if( ustack ) new_buf = realloc( ustack, new_size ); else new_buf = malloc( new_size ); if( !new_buf ) { show_strerror( 0, errno ); set_error_msg( mem_msg ); free_undo_stack(); enable_interrupts(); return 0; } usize = new_size; ustack = (undo_atom *)new_buf; } ustack[u_len].type = type; ustack[u_len].tail = search_line_node( to ); ustack[u_len].head = search_line_node( from ); enable_interrupts(); return ustack + u_len++; } /* undo last change to the editor buffer */ bool undo( const bool isglobal ) { int n; const int o_current_addr = current_addr_; const int o_last_addr = last_addr_; const bool o_modified = modified(); if( u_len <= 0 || u_current_addr < 0 || u_last_addr < 0 ) { set_error_msg( "Nothing to undo" ); return false; } search_line_node( 0 ); /* reset cached value */ disable_interrupts(); for( n = u_len - 1; n >= 0; --n ) { switch( ustack[n].type ) { case UADD: link_nodes( ustack[n].head->q_back, ustack[n].tail->q_forw ); break; case UDEL: link_nodes( ustack[n].head->q_back, ustack[n].head ); link_nodes( ustack[n].tail, ustack[n].tail->q_forw ); break; case UMOV: case VMOV: link_nodes( ustack[n-1].head, ustack[n].head->q_forw ); link_nodes( ustack[n].tail->q_back, ustack[n-1].tail ); link_nodes( ustack[n].head, ustack[n].tail ); --n; break; } ustack[n].type ^= 1; } /* reverse undo stack order */ for( n = 0; n < u_len - 1 - n; ++n ) { undo_atom tmp = ustack[n]; ustack[n] = ustack[u_len-1-n]; ustack[u_len-1-n] = tmp; } if( isglobal ) clear_active_list(); current_addr_ = u_current_addr; u_current_addr = o_current_addr; last_addr_ = u_last_addr; u_last_addr = o_last_addr; modified_ = u_modified; u_modified = o_modified; enable_interrupts(); return true; } ed_1.21.1/NEWS0000644000175000017500000000043114770103041012572 0ustar dogslegdogslegChanges in version 1.21.1: Fix a compilation failure caused by the inclusion of the unused and obsolete header . (Reported by Michael Mikonos). Ed now reads the initial window size for the z command from the environment variable LINES. (Suggested by Artyom Bologov). ed_1.21.1/COPYING0000644000175000017500000004307614666070013013150 0ustar dogslegdogsleg GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ed_1.21.1/ed.h0000644000175000017500000001326714767571623012674 0ustar dogslegdogsleg/* Global declarations for the ed editor. */ /* GNU ed - The GNU line editor. Copyright (C) 1993, 1994 Andrew L. Moore, Talke Studio Copyright (C) 2006-2025 Antonio Diaz Diaz. 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include enum Pflags /* print suffixes */ { pf_l = 0x01, /* list after command */ pf_n = 0x02, /* enumerate after command */ pf_p = 0x04 /* print after command */ }; typedef struct line_node /* Line node */ { struct line_node * q_forw; struct line_node * q_back; long pos; /* position of text in scratch buffer */ int len; /* length of line ('\n' is not stored) */ } line_node; typedef struct /* Undo atom */ { enum { UADD = 0, UDEL = 1, UMOV = 2, VMOV = 3 } type; line_node * head; /* head of list */ line_node * tail; /* tail of list */ } undo_atom; #ifndef max #define max( a, b ) ( (( a ) > ( b )) ? ( a ) : ( b ) ) #endif #ifndef min #define min( a, b ) ( (( a ) < ( b )) ? ( a ) : ( b ) ) #endif static const char * const mem_msg = "Memory exhausted"; static const char * const no_prev_subst = "No previous substitution"; /* defined in buffer.c */ bool append_lines( const char ** const ibufpp, const int addr, bool insert, const bool isglobal ); bool close_sbuf( void ); bool copy_lines( const int first_addr, const int second_addr, const int addr ); int current_addr( void ); int dec_addr( int addr ); bool delete_lines( const int from, const int to, const bool isglobal ); int get_line_node_addr( const line_node * const lp ); char * get_sbuf_line( const line_node * const lp ); int inc_addr( int addr ); int inc_current_addr( void ); bool init_buffers( void ); bool isbinary( void ); bool join_lines( const int from, const int to, const bool isglobal ); int last_addr( void ); bool modified( void ); bool warned( void ); bool move_lines( const int first_addr, const int second_addr, const int addr, const bool isglobal ); bool open_sbuf( void ); int path_max( const char * filename ); bool put_lines( const int addr ); const char * put_sbuf_line( const char * const buf, const int size ); line_node * search_line_node( const int addr ); void set_binary( void ); void set_current_addr( const int addr ); void set_modified( const bool b ); void set_warned( const bool b ); bool yank_lines( const int from, const int to ); void clear_undo_stack( void ); undo_atom * push_undo_atom( const int type, const int from, const int to ); void reset_undo_state( void ); bool undo( const bool isglobal ); /* defined in global.c */ void clear_active_list( void ); const line_node * next_active_node( void ); bool set_active_node( const line_node * const lp ); void unset_active_nodes( const line_node * bp, const line_node * const ep ); /* defined in io.c */ bool get_extended_line( const char ** const ibufpp, int * const lenp, const bool strip_escaped_newlines ); const char * get_stdin_line( int * const sizep ); int linenum( void ); bool print_lines( int from, const int to, const int pflags ); int read_file( const char * const filename, const int addr, bool * const read_onlyp ); int write_file( const char * const filename, const char * const mode, const int from, const int to ); void reset_unterminated_line( void ); void unmark_unterminated_line( const line_node * const lp ); /* defined in main.c */ bool extended_regexp( void ); bool interactive(); bool may_access_filename( const char * const name ); void print_filename( const char * const filename, const bool to_stdout ); bool restricted( void ); bool scripted( void ); void show_strerror( const char * const filename, const int errcode ); void show_warning( const char * const filename, const char * const msg ); bool strip_cr( void ); bool traditional( void ); /* defined in main_loop.c */ const char * error_msg( void ); int first_e_command( const char * const filename ); void invalid_address( void ); int main_loop( const bool initial_error, const bool loose ); bool set_def_filename( const char * const s ); void set_error_msg( const char * const msg ); bool set_prompt( const char * const s ); void set_verbose( void ); void unmark_line_node( const line_node * const lp ); /* defined in regex.c */ bool build_active_list( const char ** const ibufpp, const int first_addr, const int second_addr, const bool match ); const char * get_pattern_for_s( const char ** const ibufpp ); bool extract_replacement( const char ** const ibufpp, const bool isglobal ); int next_matching_node_addr( const char ** const ibufpp ); bool search_and_replace( const int first_addr, const int second_addr, const int snum, const bool isglobal ); bool set_subst_regex( const char * const pat, const bool ignore_case ); bool replace_subst_re_by_search_re( void ); bool subst_regex( void ); /* defined in signal.c */ void disable_interrupts( void ); void enable_interrupts( void ); const char * home_directory( void ); bool resize_buffer( char ** const buf, int * const size, const unsigned min_size ); void set_signals( void ); void set_window_lines( const int lines ); int window_columns( void ); int window_lines( void ); ed_1.21.1/io.c0000644000175000017500000002532614767571623012705 0ustar dogslegdogsleg/* io.c: i/o routines for the ed line editor */ /* GNU ed - The GNU line editor. Copyright (C) 1993, 1994 Andrew L. Moore, Talke Studio Copyright (C) 2006-2025 Antonio Diaz Diaz. 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "ed.h" static const line_node * unterminated_line = 0; /* last line has no '\n' */ static int linenum_ = 0; /* script line number */ int linenum( void ) { return linenum_; } void reset_unterminated_line( void ) { unterminated_line = 0; } void unmark_unterminated_line( const line_node * const lp ) { if( unterminated_line == lp ) unterminated_line = 0; } static bool unterminated_last_line( void ) { return ( unterminated_line != 0 && unterminated_line == search_line_node( last_addr() ) ); } /* print text to stdout */ static void print_line( const char * p, int len, const int pflags ) { const char escapes[] = "\a\b\f\n\r\t\v"; const char escchars[] = "abfnrtv"; int col = 0; if( pflags & pf_n ) { printf( "%d\t", current_addr() ); col = 8; } while( --len >= 0 ) { const unsigned char ch = *p++; if( !( pflags & pf_l ) ) putchar( ch ); else { if( ++col > window_columns() ) { col = 1; fputs( "\\\n", stdout ); } if( ch >= 32 && ch <= 126 ) { if( ch == '$' || ch == '\\' ) { ++col; putchar('\\'); } putchar( ch ); } else { char * const p = strchr( escapes, ch ); ++col; putchar('\\'); if( ch && p ) putchar( escchars[p-escapes] ); else { col += 2; putchar( ( ( ch >> 6 ) & 7 ) + '0' ); putchar( ( ( ch >> 3 ) & 7 ) + '0' ); putchar( ( ch & 7 ) + '0' ); } } } } if( !traditional() && ( pflags & pf_l ) ) putchar('$'); putchar('\n'); } /* print a range of lines to stdout */ bool print_lines( int from, const int to, const int pflags ) { line_node * const ep = search_line_node( inc_addr( to ) ); line_node * bp = search_line_node( from ); if( !from ) { invalid_address(); return false; } while( bp != ep ) { const char * const s = get_sbuf_line( bp ); if( !s ) return false; set_current_addr( from++ ); print_line( s, bp->len, pflags ); bp = bp->q_forw; } return true; } /* return the parity of escapes at the end of a string */ static bool trailing_escape( const char * const s, int len ) { bool odd_escape = false; while( --len >= 0 && s[len] == '\\' ) odd_escape = !odd_escape; return odd_escape; } /* If *ibufpp contains an escaped newline, get an extended line (one with escaped newlines) from stdin. The backslashes escaping the newlines are stripped. Return line length in *lenp, including the trailing newline. */ bool get_extended_line( const char ** const ibufpp, int * const lenp, const bool strip_escaped_newlines ) { static char * buf = 0; static int bufsz = 0; int len; for( len = 0; (*ibufpp)[len++] != '\n'; ) ; if( len < 2 || !trailing_escape( *ibufpp, len - 1 ) ) { if( lenp ) *lenp = len; return true; } if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return false; memcpy( buf, *ibufpp, len ); --len; buf[len-1] = '\n'; /* strip trailing esc */ if( strip_escaped_newlines ) --len; /* strip newline */ while( true ) { int len2; const char * const s = get_stdin_line( &len2 ); if( !s ) return false; /* error */ if( len2 <= 0 ) return false; /* EOF */ if( !resize_buffer( &buf, &bufsz, len + len2 + 1 ) ) return false; memcpy( buf + len, s, len2 ); len += len2; if( len2 < 2 || !trailing_escape( buf, len - 1 ) ) break; --len; buf[len-1] = '\n'; /* strip trailing esc */ if( strip_escaped_newlines ) --len; /* strip newline */ } buf[len] = 0; *ibufpp = buf; if( lenp ) *lenp = len; return true; } /* Read a line of text from stdin. Incomplete lines (lacking the trailing newline) are discarded. Return pointer to buffer and line size (including trailing newline), or 0 if error, or *sizep = 0 if EOF. */ const char * get_stdin_line( int * const sizep ) { static char * buf = 0; static int bufsz = 0; int i = 0; while( true ) { const int c = getchar(); if( !resize_buffer( &buf, &bufsz, i + 2 ) ) { *sizep = 0; return 0; } if( c == EOF ) { if( ferror( stdin ) ) { show_strerror( "stdin", errno ); set_error_msg( "Cannot read stdin" ); clearerr( stdin ); *sizep = 0; return 0; } if( feof( stdin ) ) { set_error_msg( "Unexpected end-of-file" ); clearerr( stdin ); buf[0] = 0; *sizep = 0; if( i > 0 ) ++linenum_; /* discard line */ return buf; } } else { buf[i++] = c; if( c == 0 ) set_binary(); if( c != '\n' ) continue; ++linenum_; buf[i] = 0; *sizep = i; return buf; } } } /* Read a line of text from a stream. Return pointer to buffer and line size (including trailing newline if it exists and is not added now). */ static const char * read_stream_line( const char * const filename, FILE * const fp, int * const sizep, bool * const newline_addedp ) { static char * buf = 0; static int bufsz = 0; int c, i = 0; while( true ) { if( !resize_buffer( &buf, &bufsz, i + 2 ) ) return 0; c = getc( fp ); if( c == EOF ) break; buf[i++] = c; if( c == 0 ) set_binary(); else if( c == '\n' ) /* remove CR only from CR/LF pairs */ { if( strip_cr() && i > 1 && buf[i-2] == '\r' ) { buf[i-2] = '\n'; --i; } break; } } buf[i] = 0; if( c == EOF ) { if( ferror( fp ) ) { show_strerror( filename, errno ); set_error_msg( "Cannot read input file" ); return 0; } else if( i ) { buf[i] = '\n'; buf[i+1] = 0; *newline_addedp = true; if( !isbinary() ) ++i; } } *sizep = i; return buf; } /* Read a stream into the editor buffer. Return number of bytes read, or -1 if error. */ static long read_stream( const char * const filename, FILE * const fp, const int addr ) { line_node * lp = search_line_node( addr ); undo_atom * up = 0; long total_size = 0; /* number of bytes read */ const bool o_isbinary = isbinary(); const bool appended = ( addr == last_addr() ); const bool o_unterminated_last_line = unterminated_last_line(); bool newline_added = false; set_current_addr( addr ); while( true ) { int size = 0; const char * const s = read_stream_line( filename, fp, &size, &newline_added ); if( !s ) return -1; if( size <= 0 ) break; total_size += size; disable_interrupts(); if( !put_sbuf_line( s, size + newline_added ) ) { enable_interrupts(); return -1; } lp = lp->q_forw; if( up ) up->tail = lp; else { up = push_undo_atom( UADD, current_addr(), current_addr() ); if( !up ) { enable_interrupts(); return -1; } } enable_interrupts(); } if( !scripted() ) { if( addr && appended && total_size && o_unterminated_last_line ) fputs( "Newline inserted\n", stdout ); /* before stream */ else if( newline_added && ( !appended || !isbinary() ) ) fputs( "Newline appended\n", stdout ); } /* after stream */ if( !appended && isbinary() && !o_isbinary && newline_added ) ++total_size; if( appended && isbinary() && ( newline_added || total_size == 0 ) ) unterminated_line = search_line_node( last_addr() ); return total_size; } /* Read a named file/pipe into the buffer. Return line count, -1 if file not found, -2 if fatal error. */ int read_file( const char * const filename, const int addr, bool * const read_onlyp ) { FILE * fp; int ret; if( *filename == '!' ) fp = popen( filename + 1, "r" ); else if( !( fp = fopen( filename, "r+" ) ) && errno != ENOENT && ( fp = fopen( filename, "r" ) ) && read_onlyp && !modified() ) *read_onlyp = true; if( !fp ) { show_strerror( filename, errno ); set_error_msg( "Cannot open input file" ); return -1; } const long size = read_stream( filename, fp, addr ); /* file size in bytes */ if( *filename == '!' ) ret = pclose( fp ); else ret = fclose( fp ); if( size < 0 ) return -2; if( ret < 0 ) { show_strerror( filename, errno ); set_error_msg( "Cannot close input file" ); return -2; } if( !scripted() ) printf( "%lu\n", size ); return current_addr() - addr; /* current_addr updated by add_line_node */ } /* Write a range of lines to a stream. Return number of bytes written, or -1 if error. */ static long write_stream( const char * const filename, FILE * const fp, int from, const int to ) { line_node * lp = search_line_node( from ); long size = 0; /* number of bytes written */ while( from && from <= to ) { int len; char * p = get_sbuf_line( lp ); if( !p ) return -1; len = lp->len; if( from != last_addr() || !isbinary() || !unterminated_last_line() ) p[len++] = '\n'; size += len; while( --len >= 0 ) if( fputc( *p++, fp ) == EOF ) { show_strerror( filename, errno ); set_error_msg( "Cannot write file" ); return -1; } ++from; lp = lp->q_forw; } return size; } /* Write a range of lines to a named file/pipe. Return line count, or -1 if error. */ int write_file( const char * const filename, const char * const mode, const int from, const int to ) { FILE * fp; int ret; if( *filename == '!' ) fp = popen( filename + 1, "w" ); else fp = fopen( filename, mode ); if( !fp ) { show_strerror( filename, errno ); set_error_msg( "Cannot open output file" ); return -1; } const long size = write_stream( filename, fp, from, to ); /* bytes written */ if( *filename == '!' ) ret = pclose( fp ); else ret = fclose( fp ); if( size < 0 ) return -1; if( ret < 0 ) { show_strerror( filename, errno ); set_error_msg( "Cannot close output file" ); return -1; } if( !scripted() ) printf( "%lu\n", size ); return ( from && from <= to ) ? to - from + 1 : 0; } ed_1.21.1/main.c0000644000175000017500000003022214770111771013175 0ustar dogslegdogsleg/* GNU ed - The GNU line editor. Copyright (C) 2006-2025 Antonio Diaz Diaz. 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Exit status: 0 for a normal exit, 1 for environmental problems (invalid command-line options, memory exhausted, command failed, etc), 2 for problems with the input file (file not found, buffer modified, I/O errors), 3 for an internal consistency error (e.g., bug) which caused ed to panic. */ /* * CREDITS * * This program is based on the editor algorithm described in * Brian W. Kernighan and P. J. Plauger's book "Software Tools * in Pascal", Addison-Wesley, 1981. * * The buffering algorithm is attributed to Rodney Ruddock of * the University of Guelph, Guelph, Ontario. * */ #include #include #include #include #include #include #include #include #include "carg_parser.h" #include "ed.h" static const char * const program_name = "ed"; static const char * const program_year = "2025"; static const char * invocation_name = "ed"; /* default value */ static bool extended_regexp_ = false; /* use EREs */ static bool quiet = false; /* suppress diagnostics */ static bool restricted_ = false; /* run in restricted mode */ static bool safe_names = true; /* reject chars 1-31 in file names */ static bool scripted_ = false; /* suppress byte counts and ! prompt */ static bool strip_cr_ = false; /* strip trailing CRs */ static bool traditional_ = false; /* be backwards compatible */ /* Access functions for command-line flags. */ bool extended_regexp( void ) { return extended_regexp_; } bool restricted( void ) { return restricted_; } bool scripted( void ) { return scripted_; } bool strip_cr( void ) { return strip_cr_; } bool traditional( void ) { return traditional_; } static void show_help( void ) { printf( "GNU ed is a line-oriented text editor. It is used to create, display,\n" "modify and otherwise manipulate text files, both interactively and via\n" "shell scripts. A restricted version of ed, red, can only edit files in\n" "the current directory and cannot execute shell commands. Ed is the\n" "'standard' text editor in the sense that it is the original editor for\n" "Unix, and thus widely available. For most purposes, however, it is\n" "superseded by full-screen editors such as GNU Emacs or GNU Moe.\n" "\nUsage: %s [options] [[+line] file]\n", invocation_name ); printf( "\nThe file name may be preceded by '+line', '+/RE', or '+?RE' to set the\n" "current line to the line number specified or to the first or last line\n" "matching the regular expression 'RE'.\n" "\nThe environment variable LINES can be used to set the initial window size.\n" "\nOptions:\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" " -E, --extended-regexp use extended regular expressions\n" " -G, --traditional run in compatibility mode\n" " -l, --loose-exit-status exit with 0 status even if a command fails\n" " -p, --prompt=STRING use STRING as an interactive prompt\n" " -q, --quiet, --silent suppress diagnostics written to stderr\n" " -r, --restricted run in restricted mode\n" " -s, --script suppress byte counts and '!' prompt\n" " -v, --verbose be verbose; equivalent to the 'H' command\n" " --strip-trailing-cr strip carriage returns at end of text lines\n" " --unsafe-names allow control characters 1-31 in file names\n" "\nStart edit by reading in 'file' if given.\n" "If 'file' begins with a '!', read output of shell command.\n" "\nExit status: 0 for a normal exit, 1 for environmental problems\n" "(invalid command-line options, memory exhausted, command failed, etc),\n" "2 for problems with the input file (file not found, buffer modified,\n" "I/O errors), 3 for an internal consistency error (e.g., bug) which caused\n" "ed to panic.\n" "\nReport bugs to bug-ed@gnu.org\n" "Ed home page: http://www.gnu.org/software/ed/ed.html\n" "General help using GNU software: http://www.gnu.org/gethelp\n" ); } static void show_version( void ) { printf( "GNU %s %s\n", program_name, PROGVERSION ); printf( "Copyright (C) 1994 Andrew L. Moore.\n" "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); printf( "License GPLv2+: GNU GPL version 2 or later \n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n" ); } void print_filename( const char * const filename, const bool to_stdout ) { FILE * const fp = to_stdout ? stdout : stderr; if( safe_names ) { fputs( filename, fp ); return; } const char * p; for( p = filename; *p; ++p ) { const unsigned char ch = *p; if( ch == '\\' ) { putc( ch, fp ); putc( ch, fp ); continue; } if( ch >= 32 ) { putc( ch, fp ); continue; } putc( '\\', fp ); putc( ( ( ch >> 6 ) & 7 ) + '0', fp ); putc( ( ( ch >> 3 ) & 7 ) + '0', fp ); putc( ( ch & 7 ) + '0', fp ); } } void show_warning( const char * const filename, const char * const msg ) { if( !quiet ) { if( filename && filename[0] ) { print_filename( filename, false ); fputs( ": ", stderr ); } fprintf( stderr, "%s\n", msg ); } } void show_strerror( const char * const filename, const int errcode ) { if( !quiet ) { if( filename && filename[0] ) { print_filename( filename, false ); fputs( ": ", stderr ); } fprintf( stderr, "%s\n", strerror( errcode ) ); } } static void show_error( const char * const msg, const int errcode, const bool help ) { if( msg && msg[0] ) fprintf( stderr, "%s: %s%s%s\n", program_name, msg, ( errcode > 0 ) ? ": " : "", ( errcode > 0 ) ? strerror( errcode ) : "" ); if( help ) fprintf( stderr, "Try '%s --help' for more information.\n", invocation_name ); } static int parse_addr( const char * const arg ) { char * tail; errno = 0; const long tmp = strtol( arg, &tail, 10 ); if( errno == 0 && tail != arg && tmp >= 1 && tmp <= INT_MAX ) return tmp; if( !quiet ) fprintf( stderr, "%s: %s: Invalid line number; must be >= 1.\n", program_name, arg ); exit( 1 ); } /* Return true if stdin is not a regular file. Piped scripts count as interactive (do not force ed to exit on error). */ bool interactive() { struct stat st; return fstat( 0, &st ) == 0 && !S_ISREG( st.st_mode ); } bool may_access_filename( const char * const name ) { const int len = strlen( name ); if( len <= 0 || name[len-1] == '/' ) { set_error_msg( "Is a directory" ); return false; } if( restricted_ ) { if( name[0] == '!' ) { set_error_msg( "Shell access restricted" ); return false; } if( strcmp( name, ".." ) == 0 || strchr( name, '/' ) ) { set_error_msg( "Directory access restricted" ); return false; } } if( safe_names ) { const char * p; for( p = name; *p; ++p ) if( *p <= 31 && *p >= 1 ) { set_error_msg( "Control characters 1-31 not allowed in file names" ); return false; } } return true; } int main( const int argc, const char * const argv[] ) { bool initial_error = false; /* fatal error reading file */ bool loose = false; enum { opt_cr = 256, opt_un }; const struct ap_Option options[] = { { 'E', "extended-regexp", ap_no }, { 'G', "traditional", ap_no }, { 'h', "help", ap_no }, { 'l', "loose-exit-status", ap_no }, { 'p', "prompt", ap_yes }, { 'q', "quiet", ap_no }, { 'q', "silent", ap_no }, { 'r', "restricted", ap_no }, { 's', "script", ap_no }, { 'v', "verbose", ap_no }, { 'V', "version", ap_no }, { opt_cr, "strip-trailing-cr", ap_no }, { opt_un, "unsafe-names", ap_no }, { 0, 0, ap_no } }; struct Arg_parser parser; if( argc > 0 ) invocation_name = argv[0]; if( !ap_init( &parser, argc, argv, options, 0 ) ) { show_error( "Memory exhausted.", 0, false ); return 1; } if( ap_error( &parser ) ) /* bad option */ { show_error( ap_error( &parser ), 0, true ); return 1; } int argind = 0; for( ; argind < ap_arguments( &parser ); ++argind ) { const int code = ap_code( &parser, argind ); const char * const arg = ap_argument( &parser, argind ); if( !code ) break; /* no more options */ switch( code ) { case 'E': extended_regexp_ = true; break; case 'G': traditional_ = true; break; /* backward compatibility */ case 'h': show_help(); return 0; case 'l': loose = true; break; case 'p': if( set_prompt( arg ) ) break; else return 1; case 'q': quiet = true; break; case 'r': restricted_ = true; break; case 's': scripted_ = true; break; case 'v': set_verbose(); break; case 'V': show_version(); return 0; case opt_cr: strip_cr_ = true; break; case opt_un: safe_names = false; break; default: show_error( "internal error: uncaught option.", 0, false ); return 3; } } /* end process options */ setlocale( LC_ALL, "" ); if( !init_buffers() ) return 1; const char * start_re_arg = 0; /* '+/RE' or '+?RE' */ int start_addr = 0; /* '+line' */ for( ; argind < ap_arguments( &parser ); ++argind ) { const char * const arg = ap_argument( &parser, argind ); /* a hyphen operand '-' is equivalent to the option '-s' */ if( strcmp( arg, "-" ) == 0 ) { scripted_ = true; continue; } if( arg[0] == '+' ) { const unsigned char ch = arg[1]; if( ch == '/' || ch == '?' ) start_re_arg = arg; /* store for later */ else if( isdigit( ch ) ) start_addr = parse_addr( arg + 1 ); else { if( !quiet ) fprintf( stderr, "%s: %s: Invalid line number or " "regular expression.\n", program_name, arg ); return 1; } continue; } if( may_access_filename( arg ) ) { if( arg[0] != '!' && !set_def_filename( arg ) ) return 1; /* first e can't be undone because u_current_addr = u_last_addr = -1 */ const int ret = first_e_command( arg ); /* line count, < 0 if error */ if( ret < 0 && !interactive() ) return 2; if( ret == -2 ) initial_error = true; if( ret > 0 && start_addr > 0 ) { if( start_addr <= last_addr() ) set_current_addr( start_addr ); } else if( ret > 0 && start_re_arg ) { set_current_addr( 0 ); /* start searching from address 0 */ const char * p = start_re_arg + 1; const int addr = next_matching_node_addr( &p ); if( addr > 0 && addr <= last_addr() ) set_current_addr( addr ); else { set_current_addr( ( start_re_arg[1] == '/' ) ? 1 : last_addr() ); if( !quiet ) fprintf( stderr, "%s: %s: No match found.\n", start_re_arg, arg ); if( !interactive() ) return 1; } } } else { initial_error = true; if( !interactive() ) return 2; } if( initial_error ) show_warning( arg, error_msg() ); break; /* extra arguments after file are ignored */ } ap_free( &parser ); return main_loop( initial_error, loose ); } ed_1.21.1/carg_parser.h0000644000175000017500000000647214735745325014571 0ustar dogslegdogsleg/* Arg_parser - POSIX/GNU command-line argument parser. (C version) Copyright (C) 2006-2025 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ /* Arg_parser reads the arguments in 'argv' and creates a number of option codes, option arguments, and non-option arguments. In case of error, 'ap_error' returns a non-null pointer to an error message. 'options' is an array of 'struct ap_Option' terminated by an element containing a code which is zero. A null long_name means a short-only option. A code value outside the unsigned char range means a long-only option. Arg_parser normally makes it appear as if all the option arguments were specified before all the non-option arguments for the purposes of parsing, even if the user of your program intermixed option and non-option arguments. If you want the arguments in the exact order the user typed them, call 'ap_init' with 'in_order' = true. The argument '--' terminates all options; any following arguments are treated as non-option arguments, even if they begin with a hyphen. The syntax of options with an optional argument is '-' (without whitespace), or '--='. The syntax of options with an empty argument is '- ""', '-- ""', or '--=""'. */ #ifdef __cplusplus extern "C" { #endif /* ap_yme = yes but maybe empty */ typedef enum ap_Has_arg { ap_no, ap_yes, ap_maybe, ap_yme } ap_Has_arg; typedef struct ap_Option { int code; /* Short option letter or code ( code != 0 ) */ const char * long_name; /* Long option name (maybe null) */ ap_Has_arg has_arg; } ap_Option; typedef struct ap_Record { int code; char * parsed_name; char * argument; } ap_Record; typedef struct Arg_parser { ap_Record * data; char * error; int data_size; int error_size; } Arg_parser; char ap_init( Arg_parser * const ap, const int argc, const char * const argv[], const ap_Option options[], const char in_order ); void ap_free( Arg_parser * const ap ); const char * ap_error( const Arg_parser * const ap ); /* The number of arguments parsed. May be different from argc. */ int ap_arguments( const Arg_parser * const ap ); /* If ap_code( i ) is 0, ap_argument( i ) is a non-option. Else ap_argument( i ) is the option's argument (or empty). */ int ap_code( const Arg_parser * const ap, const int i ); /* Full name of the option parsed (short or long). */ const char * ap_parsed_name( const Arg_parser * const ap, const int i ); const char * ap_argument( const Arg_parser * const ap, const int i ); #ifdef __cplusplus } #endif ed_1.21.1/README0000644000175000017500000001201314736572031012765 0ustar dogslegdogslegSee the file INSTALL for compilation and installation instructions. Description GNU ed is a line-oriented text editor. It is used to create, display, modify and otherwise manipulate text files, both interactively and via shell scripts. A restricted version of ed, red, can only edit files in the current directory and cannot execute shell commands. Ed is the 'standard' text editor in the sense that it is the original editor for Unix, and thus widely available. For most purposes, however, it is superseded by full-screen editors such as GNU Emacs or GNU Moe. Extensions to and deviations from the POSIX standard are described below. Report bugs to bug-ed@gnu.org Ed home page: http://www.gnu.org/software/ed/ed.html For a description of the ed algorithm, see Brian W. Kernighan and P. J. Plauger's book "Software Tools in Pascal", Addison-Wesley, 1981. Ed uses Arg_parser for command-line argument parsing: http://www.nongnu.org/arg-parser/arg_parser.html GNU ed(1) is not strictly POSIX compliant, as described in the POSIX 1003.1-2004 document. The following is a summary of omissions and extensions to, and deviations from, the POSIX standard. EXTENSIONS ---------- * Though GNU ed is not a stream editor, it can be used to edit binary files. To assist in binary editing, when a file containing at least one ASCII NUL character is written, a newline is not appended if it did not already contain one upon reading. If the last line has been modified, reading an empty file, for example /dev/null, prior to writing prevents appending a newline to a binary file. For example, to create a file with GNU ed containing a single NUL character: $ ed file a ^@ . r /dev/null wq Similarly, to remove a newline from the end of binary 'file': $ ed file r /dev/null wq * BSD commands have been implemented wherever they do not conflict with the POSIX standard. The BSD-ism's included are: * 's' (i.e., s[1-9rgp]*) to repeat a previous substitution, * 'W' for appending text to an existing file, * 'wq' for exiting after a write, and * 'z' for scrolling through the buffer. * The POSIX interactive global commands 'G' and 'V' are extended to support multiple commands, including 'a', 'c', and 'i'. The command format is the same as for the global commands 'g' and 'v', i.e., one command per line with each line, except for the last, ending in a backslash (\). * For SunOS ed(1) compatibility, GNU ed runs in restricted mode if invoked as red. This limits editing of files in the local directory only and prohibits shell commands. DEVIATIONS ---------- * To support the BSD 's' command (see EXTENSIONS above), substitution patterns cannot be delimited by the digits '1' to '9' or by the characters 'r', 'g' and 'p'. In contrast, POSIX specifies that any character except space and newline can be used as a delimiter. * Since the behavior of 'u' (undo) within a 'g' (global) command list is not specified by POSIX, GNU ed follows the behavior of the SunOS ed: undo forces a global command list to be executed only once, rather than for each line matching a global pattern. In addtion, each instance of 'u' within a global command undoes all previous commands (including undo's) in the command list. This seems the best way, since the alternatives are either too complicated to implement or too confusing to use. * The 'm' (move) command within a 'g' command list also follows the SunOS ed implementation: any lines moved are removed from the global command's 'active' list. * For backwards compatibility, errors in piped scripts do not force ed to exit. POSIX only specifies ed's response for input via regular files (including here documents) or tty's. TESTSUITE --------- The files in the 'testsuite' directory with extensions '.ed', '.r', and '.err' are used for testing ed. To run the tests, configure the package and type 'make check' from the build directory. The tests do not exhaustively check POSIX compliance nor do they check correct 8-bit or long line support. The test file extensions have the following meanings: .ed Ed script - a list of ed commands. .r Result - the expected output after processing data via an ed script. .err Error - invalid ed commands that should generate an error. The output of the .ed scripts is written to files with .o extension and compared with their corresponding .r result files. The .err scripts should exit with nonzero status without altering the contents of the buffer. If any test fails, the error messages look like: *** The script u.ed exited abnormally *** or: *** Output u.o of script u.ed is incorrect *** Copyright (C) 1993, 1994 Andrew L. Moore Copyright (C) 2006-2025 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute, and modify it. The file Makefile.in is a data file used by configure to produce the Makefile. It has the same copyright owner and permissions that configure itself. ed_1.21.1/doc/0000775000175000017500000000000000000000000012610 5ustar dogslegdogsleged_1.21.1/doc/ed.info0000644000175000017500000021632414770112012014115 0ustar dogslegdogslegThis is ed.info, produced by makeinfo version 4.13+ from ed.texi. INFO-DIR-SECTION Basics START-INFO-DIR-ENTRY * Ed: (ed). The GNU line editor END-INFO-DIR-ENTRY Copyright (C) 1993, 1994, 2006-2025 Free Software Foundation, Inc. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.  File: ed.info, Node: Top, Next: Overview, Up: (dir) The GNU ed line editor ********************** This manual is for GNU ed (version 1.21.1, 24 March 2025). * Menu: * Overview:: Overview of the 'ed' command * Introduction to line editing:: Getting started with GNU 'ed' * Invoking ed:: Command-line interface * Argument syntax:: By convention, options start with a hyphen * Line addressing:: Specifying lines/ranges in the buffer * Regular expressions:: Patterns for selecting text * Commands:: Commands recognized by GNU 'ed' * The 's' Command:: Substitute command * Limitations:: Intrinsic limits of GNU 'ed' * Diagnostics:: GNU 'ed' error handling * Problems:: Reporting bugs * GNU Free Documentation License:: How you can copy and share this manual Written by Andrew L. Moore, Franois Pinard, and Antonio Diaz Diaz Copyright (C) 1993, 1994, 2006-2025 Free Software Foundation, Inc. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.  File: ed.info, Node: Overview, Next: Introduction to line editing, Prev: Top, Up: Top 1 Overview ********** GNU ed is a line-oriented text editor. It is used to create, display, modify and otherwise manipulate text files, both interactively and via shell scripts. A restricted version of ed, red, can only edit files in the current directory and cannot execute shell commands. Ed is the 'standard' text editor in the sense that it is the original editor for Unix, and thus widely available. For most purposes, however, it is superseded by full-screen editors such as GNU Emacs or GNU Moe. GNU ed is based on the editor algorithm described in Brian W. Kernighan and P. J. Plauger's book "Software Tools in Pascal", Addison-Wesley, 1981. If invoked with a FILE argument, then a copy of FILE is read into the editor's buffer. Changes are made to this copy and not directly to FILE itself. Upon quitting 'ed', any changes not explicitly saved with a 'w' command are lost. In interactive mode, a non-existing FILE is reported but does not alter the exit status. Editing is done in two distinct modes: "command" and "input". When first invoked, 'ed' is in command mode. In this mode commands are read from the standard input and executed to manipulate the contents of the editor buffer. A typical command might look like: ,s/OLD/NEW/g which replaces all occurences of the string OLD with NEW. When an input command, such as 'a' (append), 'i' (insert) or 'c' (change), is given, 'ed' enters input mode. This is the primary means of adding text to a file. In this mode, no commands are available; instead, the standard input is written directly to the editor buffer. A "line" consists of the text up to and including a character. Input mode is terminated by entering a single period ('.') on a line. Any change made to the buffer sets the modified flag even if the change does not cause a change in the contents of the buffer; for example 's/a/a/'. Reading an empty file ('r FILE') or terminating a command before it makes any change to the buffer ('a.') does not change the buffer, and therefore does not set the modified flag. All 'ed' commands operate on whole lines or ranges of lines; e.g., the 'd' command deletes lines; the 'm' command moves lines, and so on. It is possible to modify only a portion of a line by means of replacement, as in the example above. However even here, the 's' command is applied to whole lines at a time. In general, 'ed' commands consist of zero or more line addresses, followed by a single character command and possibly additional parameters; i.e., commands have the structure: [ADDRESS[,ADDRESS]]COMMAND[PARAMETERS] The ADDRESSes indicate the line or range of lines to be affected by the command. If fewer addresses are given than the command accepts, then default addresses are supplied.  File: ed.info, Node: Introduction to line editing, Next: Invoking ed, Prev: Overview, Up: Top 2 Introduction to line editing ****************************** 'ed' was created, along with the Unix operating system, by Ken Thompson and Dennis Ritchie. It is the refinement of its more complex, programmable predecessor, 'QED', to which Thompson and Ritchie had already added pattern matching capabilities (*note Regular expressions::). For the purposes of this tutorial, a working knowledge of the Unix shell 'sh' and the Unix file system is recommended, since 'ed' is designed to interact closely with them. (*Note GNU bash manual: (bash)Top, for details about bash). The principal difference between line editors and display editors is that display editors provide instant feedback to user commands, whereas line editors require sometimes lengthy input before any effects are seen. The advantage of instant feedback, of course, is that if a mistake is made, it can be corrected immediately, before more damage is done. Editing in 'ed' requires more strategy and forethought; but if you are up to the task, it can be quite efficient. Much of the 'ed' command syntax is shared with other Unix utilities. As with the shell, (the carriage-return key) enters a line of input. So when we speak of "entering" a command or some text in 'ed', is implied at the end of each line. Prior to typing , corrections to the line may be made by typing either to erase characters backwards, or -u (i.e., hold the CONTROL key and type u) to erase the whole line. When 'ed' first opens, it expects to be told what to do but doesn't prompt us like the shell. So let's begin by telling 'ed' to do so with the

("prompt") command: $ ed P * By default, 'ed' uses asterisk ('*') as command prompt to avoid confusion with the shell command prompt ('$'). We can run Unix shell ('sh') commands from inside 'ed' by prefixing them with (exclamation mark, aka "bang"). For example: *!date Mon Jun 26 10:08:41 PDT 2006 ! *!for s in hello world; do echo $s; done hello world ! * So far, this is no different from running commands in the Unix shell. But let's say we want to edit the output of a command, or save it to a file. First we must capture the command output to a temporary location called a "buffer" where 'ed' can access it. This is done with 'ed''s command (mnemonic: "read"): *r !cal -m 137 * Here 'ed' is telling us that it has just read 137 characters into the editor buffer - i.e., the output of the 'cal' command, which prints a simple ASCII calendar. To display the buffer contents we issue the

("print") command (not to be confused with the prompt command, which is uppercase!). To indicate the range of lines in the buffer that should be printed, we prefix the command with <,> (comma) which is shorthand for "the whole buffer": *,p June 2006 Mo Tu We Th Fr Sa Su 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 * Now let's write the buffer contents to a file named 'junk' with the ("write") command: *w junk 137 * Need we say? It's good practice to frequently write the buffer contents, since unwritten changes to the buffer will be lost when we exit 'ed'. The sample sessions below illustrate some basic concepts of line editing with 'ed'. We begin by creating a file, 'sonnet', with some help from Shakespeare. As with the shell, all input to 'ed' must be followed by a character. Commands beginning with '#' are taken as comments and ignored. Input mode lines that begin with '#' are just more input. $ ed # The 'a' command is for appending text to the editor buffer. a No more be grieved at that which thou hast done. Roses have thorns, and filvers foutians mud. Clouds and eclipses stain both moon and sun, And loathsome canker lives in sweetest bud. . # Entering a single period on a line returns 'ed' to command mode. # Now write the buffer to the file 'sonnet' and quit: w sonnet 183 # 'ed' reports the number of characters written. q $ ls -l total 2 -rw-rw-r-- 1 alm 183 Nov 10 01:16 sonnet $ In the next example, some typos are corrected in the file 'sonnet'. $ ed sonnet 183 # Begin by printing the buffer to the terminal with the 'p' command. # The ',' means "all lines". ,p No more be grieved at that which thou hast done. Roses have thorns, and filvers foutians mud. Clouds and eclipses stain both moon and sun, And loathsome canker lives in sweetest bud. # Select line 2 for editing. 2 Roses have thorns, and filvers foutians mud. # Use the substitute command, 's', to replace 'filvers' with 'silver', # and print the result. s/filvers/silver/p Roses have thorns, and silver foutians mud. # And correct the spelling of 'fountains'. s/utia/untai/p Roses have thorns, and silver fountains mud. w sonnet 183 q $ Since 'ed' is line-oriented, we have to tell it which line, or range of lines we want to edit. In the example above, we do this by specifying the line's number, or sequence in the buffer. Alternatively, we could have specified a unique string in the line, e.g., '/filvers/', where the '/'s delimit the string in question. Subsequent commands affect only the selected line, a.k.a. the "current" line. Portions of that line are then replaced with the substitute command, whose syntax is 's/OLD/NEW/'. Although 'ed' accepts only one command per line, the print command 'p' is an exception, and may be appended to the end of most commands. In the next example, a title is added to our sonnet. $ ed sonnet 183 a Sonnet #50 . ,p No more be grieved at that which thou hast done. Roses have thorns, and silver fountains mud. Clouds and eclipses stain both moon and sun, And loathsome canker lives in sweetest bud. Sonnet #50 # The title got appended to the end; we should have used '0a' # to append "before the first line". # Move the title to its proper place. 5m0p Sonnet #50 # The title is now the first line, and the current address has been # set to the address of this line as well. ,p Sonnet #50 No more be grieved at that which thou hast done. Roses have thorns, and silver fountains mud. Clouds and eclipses stain both moon and sun, And loathsome canker lives in sweetest bud. wq sonnet 195 $ When 'ed' opens a file, the current address is initially set to the address of the last line of that file. Similarly, the move command 'm' sets the current address to the address of the last line moved. Related programs or routines are 'vi'(1), 'sed'(1), 'regex'(3), 'sh'(1). Relevant documents are: Unix User's Manual Supplementary Documents: 12 -- 13 B. W. Kernighan and P. J. Plauger: "Software Tools in Pascal", Addison-Wesley, 1981.  File: ed.info, Node: Invoking ed, Next: Argument syntax, Prev: Introduction to line editing, Up: Top 3 Invoking ed ************* The format for running 'ed' is: ed [OPTIONS] [[+LINE] FILE] red [OPTIONS] [[+LINE] FILE] ed [OPTIONS] [[+LINE] '!COMMAND [ARGUMENTS]'] FILE specifies the name of a file to read. If FILE is prefixed with a bang (!), then it is interpreted as a shell command. In this case, what is read is the standard output of FILE executed via 'sh'. To read a file whose name begins with a bang (or a hyphen), prefix the name with './'. The default filename is set to FILE only if it is not prefixed with a bang. The file name may be preceded by '+LINE' to set the current line to the line number specified. If LINE exceeds the number of lines in the file, the current line is set to the last line. The file name may be preceded by '+/RE' to set the current line to the first line matching the regular expression RE, or by '+?RE' to set the current line to the last line matching RE. If RE does not match any line, ed fails immediately with exit status 1 unless the option '-l' is specified and standard input is not a regular file. 'ed' supports the following options: *Note Argument syntax::. '-h' '--help' Print an informative help message describing the options and exit. '-V' '--version' Print the version number of 'ed' on the standard output and exit. This version number should be included in all bug reports. '-E' '--extended-regexp' Use extended regular expressions instead of the basic regular expressions mandated by POSIX. '-G' '--traditional' Forces backwards compatibility. This affects the behavior of the 'ed' commands 'G', 'V', 'f', 'l', 'm', 't', and '!!'. If the default behavior of these commands does not seem familiar, then try invoking 'ed' with this switch. '-l' '--loose-exit-status' Don't exit with bad status if a command happens to "fail" (for example if a substitution command finds nothing to replace). This can be useful when 'ed' is invoked as the editor for crontab. '-p STRING' '--prompt=STRING' Specifies a command prompt string and turns prompting on. Showing the prompt string may be toggled on and off with the 'P' command. '-q' '--quiet' '--silent' Suppress diagnostic messages written to standard error. '-r' '--restricted' Run in restricted mode. This mode disables editing of files out of the current directory and execution of shell commands. '-s' '--script' Suppress the printing of byte counts by 'e', 'E', 'r', and 'w' commands, and the '!' prompt after a '!' command. Suppress also the messages "Newline inserted" and "Newline appended". This option does not suppress diagnostic messages written to standard error (see '-q' above). '-s' may be useful if 'ed''s standard input is from a script. '-v' '--verbose' Turn help mode on; print a help message explaining the reason for each '?' notification. This may be toggled on and off with the 'H' command. Use this option to aid in debugging ed scripts. '--strip-trailing-cr' Strip the carriage returns at the end of text lines in DOS files. CRs are removed only from the CR/LF (carriage return/line feed) pair ending the line. CRs at other positions in the line, including a CR finishing an unterminated line, are not removed. The CRs are not restored when saving the buffer to a file. '--unsafe-names' 'ed' rejects file names containing control characters 1 to 31 unless they are allowed with this option. Exit status: 0 for a normal exit, 1 for environmental problems (invalid command-line options, memory exhausted, command failed, etc), 2 for problems with the input file (file not found, buffer modified, I/O errors), 3 for an internal consistency error (e.g., bug) which caused ed to panic.  File: ed.info, Node: Argument syntax, Next: Line addressing, Prev: Invoking ed, Up: Top 4 Syntax of command-line arguments ********************************** POSIX recommends these conventions for command-line arguments. * A command-line argument is an option if it begins with a hyphen ('-'). * Option names are single alphanumeric characters. * Certain options require an argument. * An option and its argument may or may not appear as separate tokens. (In other words, the whitespace separating them is optional). Thus, '-o foo' and '-ofoo' are equivalent. * One or more options without arguments, followed by at most one option that takes an argument, may follow a hyphen in a single token. Thus, '-abc' is equivalent to '-a -b -c'. * Options typically precede other non-option arguments. * The argument '--' terminates all options; any following arguments are treated as non-option arguments, even if they begin with a hyphen. * A token consisting of a single hyphen character is interpreted as an ordinary non-option argument. By convention, it is used to specify standard input, standard output, or a file named '-'. GNU adds "long options" to these conventions: * A long option consists of two hyphens ('--') followed by a name made of alphanumeric characters and hyphens. Option names are typically one to three words long, with hyphens to separate words. Abbreviations can be used for the long option names as long as the abbreviations are unique. * A long option and its argument may or may not appear as separate tokens. In the latter case they must be separated by an equal sign '='. Thus, '--foo bar' and '--foo=bar' are equivalent.  File: ed.info, Node: Line addressing, Next: Regular expressions, Prev: Argument syntax, Up: Top 5 Line addressing ***************** An address represents the number of a line in the buffer. 'ed' maintains a "current address" which is typically supplied to commands as the default address when none is specified. When a file is first read, the current address is set to the address of the last line of the file. In general, the current address is set to the address of the last line affected by a command. One exception to the rule that addresses represent line numbers is the address '0' (zero). This means "at the beginning of the buffer", and is valid wherever it makes sense. An address range is two addresses separated either by a comma (',') or a semicolon (';'). The value of the first address in a range cannot exceed the value of the second. In a semicolon-delimited range, the current address ('.') is set to the first address before the second address is calculated. This feature can be used to set the starting line for searches if the second address contains a regular expression. The address '0' (zero) is valid as a starting point so that '0;/RE/' can match RE in the first line of the buffer. Addresses can be omitted on either side of the comma or semicolon separator. If only the first address is given in a range, then the second address is set to the given address. If only the second address is given, the resulting address pairs are '1,addr' and '.;addr' respectively. If a N-tuple of addresses is given where N > 2, then the corresponding range is determined by the last two addresses in the N-tuple. If only one address is expected, then the last address is used. It is an error to give any number of addresses to a command that requires zero addresses. A line address is constructed as follows: '.' The current line (address) in the buffer. '$' The last line in the buffer. 'N' The Nth line in the buffer, where N is a number in the range '0,$'. '+N' The Nth next line, where N is a non-negative number. '-N' The Nth previous line, where N is a non-negative number. '+' The next line. This is equivalent to '+1' and may be repeated with cumulative effect. '-' The previous line. This is equivalent to '-1' and may be repeated with cumulative effect. ',' The first through last lines in the buffer. This is equivalent to the address range '1,$'. ';' The current through last lines in the buffer. This is equivalent to the address range '.;$'. '/RE/[I]' The next line containing the regular expression RE. The search wraps to the beginning of the buffer and continues down to the current line, if necessary. The suffix 'I' is a GNU extension which makes 'ed' match RE in a case-insensitive manner. '?RE?[I]' The previous line containing the regular expression RE. The search wraps to the end of the buffer and continues up to the current line, if necessary. The suffix 'I' is a GNU extension which makes 'ed' match RE in a case-insensitive manner. ''x' The apostrophe-x character pair addresses the line previously marked by a 'k' (mark) command, where 'x' is a lower case letter from the portable character set '[a-z]'. Addresses can be followed by one or more address offsets, optionally separated by whitespace. Offsets are constructed as follows: * '+' or '-' followed by a number adds or subtracts the indicated number of lines to or from the address. * '+' or '-' not followed by a number adds or subtracts 1 to or from the address. * A number adds the indicated number of lines to the address. It is not an error if an intermediate address value is negative or greater than the address of the last line in the buffer. It is an error if the final address value is negative or greater than the address of the last line in the buffer. It is an error if a search for a regular expression fails to find a matching line.  File: ed.info, Node: Regular expressions, Next: Commands, Prev: Line addressing, Up: Top 6 Regular expressions ********************* Regular expressions are patterns used in selecting text. For example, the 'ed' command g/STRING/ prints all lines containing STRING. Regular expressions are also used by the 's' command for selecting old text to be replaced with new text. In addition to specifying string literals, regular expressions can represent classes of strings. Strings thus represented are said to be matched by the corresponding regular expression. If it is possible for a regular expression to match several strings in a line, then the left-most match is the one selected. If the regular expression permits a variable number of matching characters, the longest sequence starting at that point is matched. An empty regular expression is equivalent to the last regular expression processed. Therefore '/RE/s//REPLACEMENT/' replaces RE with REPLACEMENT. As a GNU extension, a regular expression /RE/ may be followed by the suffix 'I' which makes 'ed' match RE in a case-insensitive manner. Note that the suffix is evaluated when the regular expression is compiled, thus it is invalid to specify it together with the empty regular expression. The following symbols are used in constructing regular expressions using POSIX basic regular expression syntax: 'C' Any character C not listed below, including '{', '}', '(', ')', '<', and '>', matches itself. '\C' Any backslash-escaped character C, other than '{', '}', '(', ')', '<', '>', 'b', 'B', 'w', 'W', '+', and '?', matches itself. '.' Matches any single character. '[CHAR-CLASS]' Matches any single character in CHAR-CLASS. To include a ']' in CHAR-CLASS, it must be the first character. A range of characters may be specified by separating the end characters of the range with a '-', e.g., 'a-z' specifies the lower case characters. The following literal expressions can also be used in CHAR-CLASS to specify sets of characters: [:alnum:] [:cntrl:] [:lower:] [:space:] [:alpha:] [:digit:] [:print:] [:upper:] [:blank:] [:graph:] [:punct:] [:xdigit:] If '-' appears as the first or last character of CHAR-CLASS, then it matches itself. All other characters in CHAR-CLASS match themselves. Patterns in CHAR-CLASS of the form: [.COL-ELM.] [=COL-ELM=] where COL-ELM is a "collating element" are interpreted according to 'locale'(5). See 'regex'(7) for an explanation of these constructs. '[^CHAR-CLASS]' Matches any single character, other than newline, not in CHAR-CLASS. CHAR-CLASS is defined as above. '^' If '^' is the first character of a regular expression, then it anchors the regular expression to the beginning of a line. Otherwise, it matches itself. '$' If '$' is the last character of a regular expression, it anchors the regular expression to the end of a line. Otherwise, it matches itself. '\(RE\)' Defines a (possibly empty) subexpression RE. Subexpressions may be nested. A subsequent backreference of the form '\N', where N is a number in the range [1,9], expands to the text matched by the Nth subexpression. For example, the regular expression '\(a.c\)\1' matches the string 'abcabc', but not 'abcadc'. Subexpressions are ordered relative to their left delimiter. '*' Matches zero or more repetitions of the regular expression immediately preceding it. The regular expression can be either a single character regular expression or a subexpression. If '*' is the first character of a regular expression or subexpression, then it matches itself. The '*' operator sometimes yields unexpected results. For example, the regular expression 'b*' matches the beginning of the string 'abbb', as opposed to the substring 'bbb', since an empty string is the only left-most match. '\{N,M\}' '\{N,\}' '\{N\}' Matches the single character regular expression or subexpression immediately preceding it at least N and at most M times. If M is omitted, then it matches at least N times. If the comma is also omitted, then it matches exactly N times. If any of these forms occurs first in a regular expression or subexpression, then it is interpreted literally (i.e., the regular expression '\{2\}' matches the string '{2}', and so on). The following extensions to basic regular expression operators are preceded by a backslash '\' to distinguish them from traditional 'ed' syntax. They may be unavailable depending on the particular regex implementation in your system. '\<' '\>' Anchors the single character regular expression or subexpression immediately following it to the beginning (in the case of '\<') or ending (in the case of '\>') of a "word", i.e., in ASCII, a maximal string of alphanumeric characters, including the underscore (_). '\`' '\'' Unconditionally matches the beginning '\`' or ending '\'' of a line. '\?' Optionally matches the single character regular expression or subexpression immediately preceding it. For example, the regular expression 'a[bd]\?c' matches the strings 'abc', 'adc' and 'ac'. If '\?' occurs at the beginning of a regular expressions or subexpression, then it matches a literal '?'. '\+' Matches the single character regular expression or subexpression immediately preceding it one or more times. So the regular expression 'a\+' is shorthand for 'aa*'. If '\+' occurs at the beginning of a regular expression or subexpression, then it matches a literal '+'. '\b' Matches the beginning or ending (empty string) of a word. Thus the regular expression '\bhello\b' is equivalent to '\'. However, '\b\b' is a valid regular expression whereas '\<\>' is not. '\B' Matches (an empty string) inside a word. '\w' Matches any word-constituent character (letters, digits, and the underscore). '\W' Matches any character that is not a word-constituent.  File: ed.info, Node: Commands, Next: The 's' Command, Prev: Regular expressions, Up: Top 7 Commands ********** All 'ed' commands are single characters, though some require additional parameters. If a command's parameters extend over several lines, then each line except for the last must be terminated with a backslash ('\'). In general, at most one command is allowed per line. However, most commands accept a print suffix, which is any of 'p' (print), 'l' (list), or 'n' (enumerate), to print the last line affected by the command. It is not portable to give more than one print suffix, but 'ed' allows any combination of non-repeated print suffixes and combines their effects. If any suffix letter is given, it must immediately follow the command. The 'e', 'E', 'f', 'r', and 'w' commands take an optional FILE parameter, separated from the command letter by one or more whitespace characters. An interrupt (typically ) has the effect of aborting the current command and returning the editor to command mode. 'ed' recognizes the following commands. The commands are shown together with the default address or address range supplied if none is specified (in parenthesis). '(.)a' Appends text to the buffer after the addressed line. The address '0' (zero) is valid for this command; it places the entered text at the beginning of the buffer. Text is entered in input mode. The current address is set to the address of the last line entered or, if there were none, to the addressed line. '(.,.)c' Changes lines in the buffer. The addressed lines are deleted from the buffer, and text is inserted in their place. Text is entered in input mode. The current address is set to the address of the last line entered or, if there were none, to the new address of the line after the last line deleted; if the lines deleted were originally at the end of the buffer, the current address is set to the address of the new last line; if no lines remain in the buffer, the current address is set to zero. The lines deleted are copied to the cut buffer. '(.,.)d' Deletes the addressed lines from the buffer. The current address is set to the new address of the line after the last line deleted; if the lines deleted were originally at the end of the buffer, the current address is set to the address of the new last line; if no lines remain in the buffer, the current address is set to zero. The lines deleted are copied to the cut buffer. 'e FILE' Edits FILE, and sets the default filename. If FILE is not specified, then the default filename is used. Any lines in the buffer are deleted before the new file is read. If FILE does not exist, a warning is printed in place of a byte count and the resulting buffer is left empty. The current address is set to the address of the last line in the buffer. If FILE is prefixed with a bang (!), then it is interpreted as a shell command whose output is to be read, (*note shell escape command:: '!' below). In this case the default filename is unchanged. To edit a file whose name begins with a bang, prefix the name with './'. A warning is printed if the buffer modified flag is set. If another 'e' or 'q' command is given with no intervening error or buffer-modifying command, it is executed without warning, and any changes to the buffer are lost. 'E FILE' Edits FILE unconditionally. This is similar to the 'e' command, except that unwritten changes are discarded without warning. 'f FILE' Sets the default filename to FILE, whether or not FILE names an existing file. If FILE is not specified, then the default filename is printed. Tilde expansion is performed on FILE; if FILE starts with '~/', the '~' is expanded to specify your home directory '$HOME'. '(1,$)g/RE/[I]COMMAND-LIST' Global command. The global command makes two passes over the file. On the first pass, all the addressed lines matching a regular expression RE are marked. The suffix 'I' is a GNU extension which makes 'ed' match RE in a case-insensitive manner. Then, going sequentially from the beginning of the file to the end of the file, the given COMMAND-LIST is executed for each marked line, with the current address set to the address of that line. Any line modified by the COMMAND-LIST is unmarked. The final value of the current address is the value assigned by the last command in the last COMMAND-LIST executed. If there were no matching lines, the current address is unchanged. The execution of COMMAND-LIST stops on the first error. The first command of COMMAND-LIST must appear on the same line as the 'g' command. The other commands of COMMAND-LIST must appear on separate lines. All lines of a multi-line COMMAND-LIST except the last line must be terminated with a backslash ('\'). Any commands are allowed, except for 'g', 'G', 'v', and 'V'. The '.' terminating the input mode of commands 'a', 'c', and 'i' can be omitted if it would be the last line of COMMAND-LIST. By default, a newline alone in COMMAND-LIST is equivalent to a 'p' command. If 'ed' is invoked with the command-line option '-G', then a newline in COMMAND-LIST is equivalent to a '.+1p' command. '(1,$)G/RE/[I]' Interactive global command. Interactively edits the addressed lines matching a regular expression RE. The suffix 'I' is a GNU extension which makes 'ed' match RE in a case-insensitive manner. For each matching line, the line is printed, the current address is set, and the user is prompted to enter a COMMAND-LIST. The final value of the current address is the value assigned by the last command executed. If there were no matching lines, the current address is unchanged. The format of COMMAND-LIST is the same as that of the 'g' command. A newline alone acts as an empty command list. A single '&' repeats the last non-empty command list. 'h' Prints a help message explaining the reason for the most recent '?' notification. 'H' Toggles the printing of help messages (see the 'h' command above). If the help mode is being turned on, also prints the help message corresponding to the most recent '?' notification. By default, help messages are not printed. '(.)i' Inserts text in the buffer before the addressed line. The address '0' (zero) is valid for this command; it places the entered text at the beginning of the buffer. Text is entered in input mode. The current address is set to the address of the last line entered or, if there were none, to the addressed line. '(.,.+1)j' Joins the addressed lines, replacing them by a single line containing their joined text. If only one address is given, this command does nothing. If lines are joined, the lines replaced are copied to the cut buffer and the current address is set to the address of the joined line. Else, the current address is unchanged. '(.)kx' Marks a line with a lower case letter 'x'. The line can then be addressed as ''x' (i.e., a single quote followed by 'x') in subsequent commands. The mark is not cleared until the line is deleted or otherwise modified. The current address is unchanged. '(.,.)l' List command. Prints the addressed lines unambiguously. The end of each line is marked with a '$', and every '$' character within the text is printed with a preceding backslash. Special characters are printed as escape sequences. The current address is set to the address of the last line printed. '(.,.)m(.)' Moves lines in the buffer. The addressed lines are moved to after the right-hand destination address. The destination address '0' (zero) is valid for this command; it moves the addressed lines to the beginning of the buffer. It is an error if the destination address falls within the range of lines to be moved. The current address is set to the new address of the last line moved. '(.,.)n' Number command. Prints the addressed lines, preceding each line by its line number and a . The current address is set to the address of the last line printed. '(.,.)p' Prints the addressed lines. The current address is set to the address of the last line printed. 'P' Toggles the command prompt on and off. Unless a prompt string is specified with the command-line option '-p', the command prompt is by default turned off. The default prompt string is an asterisk ('*'). 'q' Quits 'ed'. A warning is printed if the buffer modified flag is set. If another 'e' or 'q' command is given with no intervening error or buffer-modifying command, it is executed without warning, and any changes to the buffer are lost. 'Q' Quits 'ed' unconditionally. This is similar to the 'q' command, except that unwritten changes are discarded without warning. '($)r FILE' Reads FILE and appends it after the addressed line. If FILE is not specified, then the default filename is used. If there is no default filename prior to the command, then the default filename is set to FILE. Otherwise, the default filename is unchanged. The address '0' (zero) is valid for this command; it reads the file at the beginning of the buffer. The current address is set to the address of the last line read or, if there were none, to the addressed line. If FILE is prefixed with a bang (!), then it is interpreted as a shell command whose output is to be read, (*note shell escape command:: '!' below). In this case the default filename is unchanged. To read a file whose name begins with a bang, prefix the name with './'. '(.,.)t(.)' Copies (i.e., transfers) the addressed lines to after the right-hand destination address. If the destination address is '0' (zero), the lines are copied at the beginning of the buffer. The current address is set to the address of the last line copied. 'u' Undoes the effect of the last command that modified anything in the buffer and restores the current address to what it was before the command. The global commands 'g', 'G', 'v', and 'V' are treated as a single command by undo. 'u' is its own inverse; it can undo only the last command. '(1,$)v/RE/[I]COMMAND-LIST' This is similar to the 'g' command except that it applies COMMAND-LIST to each of the addressed lines not matching the regular expression RE. '(1,$)V/RE/[I]' This is similar to the 'G' command except that it interactively edits the addressed lines not matching the regular expression RE. '(1,$)w FILE' Writes the addressed lines to FILE. Any previous contents of FILE are lost without warning. If there is no default filename, then the default filename is set to FILE, otherwise it is unchanged. If no filename is specified, then the default filename is used. If the whole buffer is written, then the buffer modified flag is cleared. The current address is unchanged. If FILE is prefixed with a bang (!), then it is interpreted as a shell command and the addressed lines are written to its standard input, (*note shell escape command:: '!' below). In this case the default filename is unchanged. Writing the buffer to a shell command does not clear the buffer modified flag. To write to a file whose name begins with a bang, prefix the name with './'. '(1,$)wq FILE' Writes the addressed lines to FILE, and then executes a 'q' command. '(1,$)W FILE' Appends the addressed lines to the end of FILE. This is similar to the 'w' command, except that the previous contents of FILE are not clobbered. The current address is unchanged. '(.)x' Copies (puts) the contents of the cut buffer to after the addressed line. The current address is set to the address of the last line copied. '(.,.)y' Copies (yanks) the addressed lines to the cut buffer. The cut buffer is overwritten by subsequent 'c', 'd', 'j', 's', or 'y' commands. The current address is unchanged. '(.+1)zN' Scroll. Prints N lines at a time starting at addressed line, and sets window size to N. If N is not specified, then the current window size is used. Window size defaults to screen size minus two lines, or to 22 if screen size can't be determined. The environment variable LINES can be used to set the initial window size. LINES and N are not limited by screen size. The current address is set to the address of the last line printed. '!COMMAND' Shell escape command. Executes COMMAND via 'sh'. If the first character of COMMAND is '!', then it is replaced by the text of the previous '!COMMAND'. Thus, '!!' repeats the previous '!COMMAND'. 'ed' does not process COMMAND for backslash ('\') escapes. However, each unescaped '%' is replaced with the default filename, and the backslash is removed from each escaped '%'. When the shell returns from execution, a '!' is printed to the standard output. The current address is unchanged. The effect of ex(1) style '!' filtering can be achieved with the following sequence of commands: ADDR1,ADDR2w !COMMAND > tmp.txt 2>&1 ADDR2r tmp.txt ADDR1,ADDR2d !rm tmp.txt '(.,.)#' Begins a comment; the rest of the line, up to a newline, is ignored. If a line address followed by a semicolon is given, then the current address is set to that address. Otherwise, the current address is unchanged. '($)=' Prints the line number of the addressed line. The current address is unchanged. '(.+1)' Null command. An address alone prints the addressed line. A alone is equivalent to '+1p'. The current address is set to the address of the printed line.  File: ed.info, Node: The 's' Command, Next: Limitations, Prev: Commands, Up: Top 8 Substitute command ******************** The substitute command 's' replaces text in the addressed lines matching a regular expression RE with REPLACEMENT. By default, only the first match in each line is replaced. The syntax of the 's' command is: (.,.)s/RE/REPLACEMENT/[SUFFIXES] The 's' command accepts any combination of the following optional suffixes: 'g' 'global': replace every match in the line, not just the first. 'COUNT' A positive number causes only the COUNTth match to be replaced. 'g' and 'COUNT' can't be specified in the same command. 'l' 'n' 'p' The usual print suffixes. *Note print suffixes::. 'I' 'i' The suffix 'I' is a GNU extension which makes 'ed' match RE in a case-insensitive manner. It is an error if no substitutions are performed on any of the addressed lines. The current address is set to the address of the last line on which a substitution occurred. If a line is split, a substitution is considered to have occurred on each of the new lines. If no substitution is performed, the current address is unchanged. The last line modified is copied to the cut buffer. RE and REPLACEMENT may be delimited by any character other than , , and the characters used by the form of the 's' command shown below. If the last delimiter is omitted, then the last line affected is printed as if the print suffix 'p' were specified. The last delimiter can't be omitted if the 's' command is part of a 'g' or 'v' COMMAND-LIST and is not the last command in the list, because the meaning of the following escaped newline would become ambiguous. An unescaped '&' in REPLACEMENT is replaced by the currently matched text. The character sequence '\M' where M is a number in the range [1,9], is replaced by the Mth backreference expression of the matched text. If the corresponding backreference expression does not match, then the character sequence '\M' is replaced by the empty string. If REPLACEMENT consists of a single '%', then REPLACEMENT from the last substitution is used. A line can be split by including a newline escaped with a backslash ('\') in REPLACEMENT. Each backslash in REPLACEMENT removes the special meaning (if any) of the following character. 'ed' can repeat the last substitution using the following alternative syntax for the 's' command: (.,.)s[SUFFIXES] This form of the 's' command accepts the suffixes 'g' and 'COUNT' described above, and any combination of the suffixes 'p' and 'r'. The suffix 'g' toggles the global suffix of the last substitution and resets COUNT to 1. The suffix 'p' toggles the print suffixes of the last substitution. The suffix 'r' causes the RE of the last search to be used instead of the RE of the last substitution (if the search happened after the substitution).  File: ed.info, Node: Limitations, Next: Diagnostics, Prev: The 's' Command, Up: Top 9 Limitations ************* If the terminal hangs up and the buffer is modified and not empty, 'ed' attempts to write the buffer to the file 'ed.hup' or, if this fails, to '$HOME/ed.hup'. If a text (non-binary) file is not terminated by a newline character, then 'ed' appends one on reading/writing it. In the case of a binary file, 'ed' does not append a newline on reading/writing. A binary file is one containing at least one ASCII NUL character. If the last line has been modified, reading an empty file, for example '/dev/null', prior to writing prevents appending a newline to a binary file. In order to keep track of the text lines in the buffer, 'ed' uses a doubly linked list of structures containing the position and size of each line. This results in a per line overhead of 2 'pointer's, 1 'long int', and 1 'int'. The maximum line length is INT_MAX - 1 bytes. The maximum number of lines is INT_MAX - 2 lines.  File: ed.info, Node: Diagnostics, Next: Problems, Prev: Limitations, Up: Top 10 Diagnostics ************** 'ed' prints two kinds of mesages: diagnostic messages (errors and warnings) written to standard error and help messages written to standard output. Diagnostic messages may be suppresed with option '-q'. Help messages may be enabled with option '-v' and may be toggled with command 'H'. 'ed' warns the first time that a command modifies a buffer loaded from a read-only file. When an error occurs, if 'ed''s standard input is a regular file or here document, then it exits, otherwise it prints a '?' and returns to command mode. An explanation of the last error can be printed with the 'h' (help) command. If the 'u' (undo) command occurs in a global command list, then the command list is executed only once.  File: ed.info, Node: Problems, Next: GNU Free Documentation License, Prev: Diagnostics, Up: Top 11 Reporting bugs ***************** There are probably bugs in 'ed'. There are certainly errors and omissions in this manual. If you report them, they will get fixed. If you don't, no one will ever know about them and they will remain unfixed for all eternity, if not longer. If you find a bug in 'ed', please send electronic mail to . Include the version number, which you can find by running 'ed --version'.  File: ed.info, Node: GNU Free Documentation License, Prev: Problems, Up: Top 12 GNU Free Documentation License ********************************* Version 1.3, 3 November 2008 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. 'http://fsf.org/' Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. The "publisher" means any person or entity that distributes copies of the Document to the public. A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements." 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. 8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License. However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it. 10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See 'http://www.gnu.org/copyleft/'. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document. 11. RELICENSING "Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC site. "CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization. "Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document. An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008. The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing. ADDENDUM: How to use this License for your documents ==================================================== To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright (C) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License''. If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this: with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.  Tag Table: Node: Top535 Node: Overview1907 Node: Introduction to line editing4811 Node: Invoking ed12078 Node: Argument syntax16000 Node: Line addressing17762 Node: Regular expressions21799 Node: Commands27984 Ref: print suffixes28319 Ref: shell escape command40844 Node: The 's' Command42148 Node: Limitations45063 Node: Diagnostics46086 Node: Problems46923 Node: GNU Free Documentation License47458  End Tag Table  Local Variables: coding: iso-8859-15 End: ed_1.21.1/doc/ed.10000644000175000017500000000566314770112013013325 0ustar dogslegdogsleg.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2. .TH ED "1" "March 2025" "GNU ed 1.21.1" "User Commands" .SH NAME ed \- line-oriented text editor .SH SYNOPSIS .B ed [\fI\,options\/\fR] [[\fI\,+line\/\fR] \fI\,file\/\fR] .SH DESCRIPTION GNU ed is a line\-oriented text editor. It is used to create, display, modify and otherwise manipulate text files, both interactively and via shell scripts. A restricted version of ed, red, can only edit files in the current directory and cannot execute shell commands. Ed is the \&'standard' text editor in the sense that it is the original editor for Unix, and thus widely available. For most purposes, however, it is superseded by full\-screen editors such as GNU Emacs or GNU Moe. .PP The file name may be preceded by '+line', '+/RE', or '+?RE' to set the current line to the line number specified or to the first or last line matching the regular expression 'RE'. .PP The environment variable LINES can be used to set the initial window size. .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR display this help and exit .TP \fB\-V\fR, \fB\-\-version\fR output version information and exit .TP \fB\-E\fR, \fB\-\-extended\-regexp\fR use extended regular expressions .TP \fB\-G\fR, \fB\-\-traditional\fR run in compatibility mode .TP \fB\-l\fR, \fB\-\-loose\-exit\-status\fR exit with 0 status even if a command fails .TP \fB\-p\fR, \fB\-\-prompt\fR=\fI\,STRING\/\fR use STRING as an interactive prompt .TP \fB\-q\fR, \fB\-\-quiet\fR, \fB\-\-silent\fR suppress diagnostics written to stderr .TP \fB\-r\fR, \fB\-\-restricted\fR run in restricted mode .TP \fB\-s\fR, \fB\-\-script\fR suppress byte counts and '!' prompt .TP \fB\-v\fR, \fB\-\-verbose\fR be verbose; equivalent to the 'H' command .TP \fB\-\-strip\-trailing\-cr\fR strip carriage returns at end of text lines .TP \fB\-\-unsafe\-names\fR allow control characters 1\-31 in file names .PP Start edit by reading in 'file' if given. If 'file' begins with a '!', read output of shell command. .PP Exit status: 0 for a normal exit, 1 for environmental problems (invalid command\-line options, memory exhausted, command failed, etc), 2 for problems with the input file (file not found, buffer modified, I/O errors), 3 for an internal consistency error (e.g., bug) which caused ed to panic. .SH "REPORTING BUGS" Report bugs to bug\-ed@gnu.org .br Ed home page: http://www.gnu.org/software/ed/ed.html .br General help using GNU software: http://www.gnu.org/gethelp .SH COPYRIGHT Copyright \(co 1994 Andrew L. Moore. .br Copyright \(co 2025 Antonio Diaz Diaz. License GPLv2+: GNU GPL version 2 or later .br This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. .SH "SEE ALSO" The full documentation for .B ed is maintained as a Texinfo manual. If the .B info and .B ed programs are properly installed at your site, the command .IP .B info ed .PP should give you access to the complete manual. ed_1.21.1/doc/fdl.texi0000644000175000017500000005601511104012440014277 0ustar dogslegdogsleg@c The GNU Free Documentation License. @center Version 1.3, 3 November 2008 @c This file is intended to be included within another document, @c hence no sectioning command or @node. @display Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. @uref{http://fsf.org/} Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @end display @enumerate 0 @item PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document @dfn{free} in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of ``copyleft'', which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. @item APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The ``Document'', below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as ``you''. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A ``Modified Version'' of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A ``Secondary Section'' is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The ``Invariant Sections'' are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The ``Cover Texts'' are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A ``Transparent'' copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not ``Transparent'' is called ``Opaque''. Examples of suitable formats for Transparent copies include plain @sc{ascii} without markup, Texinfo input format, La@TeX{} input format, @acronym{SGML} or @acronym{XML} using a publicly available @acronym{DTD}, and standard-conforming simple @acronym{HTML}, PostScript or @acronym{PDF} designed for human modification. Examples of transparent image formats include @acronym{PNG}, @acronym{XCF} and @acronym{JPG}. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, @acronym{SGML} or @acronym{XML} for which the @acronym{DTD} and/or processing tools are not generally available, and the machine-generated @acronym{HTML}, PostScript or @acronym{PDF} produced by some word processors for output purposes only. The ``Title Page'' means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, ``Title Page'' means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. The ``publisher'' means any person or entity that distributes copies of the Document to the public. A section ``Entitled XYZ'' means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as ``Acknowledgements'', ``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title'' of such a section when you modify the Document means that it remains a section ``Entitled XYZ'' according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. @item VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. @item COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. @item MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: @enumerate A @item Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. @item List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. @item State on the Title page the name of the publisher of the Modified Version, as the publisher. @item Preserve all the copyright notices of the Document. @item Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. @item Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. @item Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. @item Include an unaltered copy of this License. @item Preserve the section Entitled ``History'', Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled ``History'' in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. @item Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the ``History'' section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. @item For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. @item Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. @item Delete any section Entitled ``Endorsements''. Such a section may not be included in the Modified Version. @item Do not retitle any existing section to be Entitled ``Endorsements'' or to conflict in title with any Invariant Section. @item Preserve any Warranty Disclaimers. @end enumerate If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled ``Endorsements'', provided it contains nothing but endorsements of your Modified Version by various parties---for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. @item COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled ``History'' in the various original documents, forming one section Entitled ``History''; likewise combine any sections Entitled ``Acknowledgements'', and any sections Entitled ``Dedications''. You must delete all sections Entitled ``Endorsements.'' @item COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. @item AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an ``aggregate'' if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. @item TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled ``Acknowledgements'', ``Dedications'', or ``History'', the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. @item TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License. However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it. @item FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See @uref{http://www.gnu.org/copyleft/}. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License ``or any later version'' applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document. @item RELICENSING ``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A ``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the site means any set of copyrightable works thus published on the MMC site. ``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization. ``Incorporate'' means to publish or republish a Document, in whole or in part, as part of another Document. An MMC is ``eligible for relicensing'' if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008. The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing. @end enumerate @page @heading ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: @smallexample @group Copyright (C) @var{year} @var{your name}. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License''. @end group @end smallexample If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the ``with@dots{}Texts.'' line with this: @smallexample @group with the Invariant Sections being @var{list their titles}, with the Front-Cover Texts being @var{list}, and with the Back-Cover Texts being @var{list}. @end group @end smallexample If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. @c Local Variables: @c ispell-local-pdict: "ispell-dict" @c End: ed_1.21.1/doc/ed.texi0000644000175000017500000013743714770111771014155 0ustar dogslegdogsleg\input texinfo @c -*-texinfo-*- @c %**start of header @setfilename ed.info @documentencoding ISO-8859-15 @settitle GNU @command{ed} Manual @finalout @c %**end of header @set UPDATED 24 March 2025 @set VERSION 1.21.1 @dircategory Basics @direntry * Ed: (ed). The GNU line editor @end direntry @copying Copyright @copyright{} 1993, 1994, 2006-2025 Free Software Foundation, Inc. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. @end copying @ifnothtml @titlepage @title GNU ed @subtitle The GNU line editor @subtitle for GNU ed version @value{VERSION}, @value{UPDATED} @author by Andrew L. Moore, Franois Pinard, and Antonio Diaz Diaz @page @vskip 0pt plus 1filll @insertcopying @end titlepage @contents @end ifnothtml @ifnottex @node Top @top The GNU ed line editor This manual is for GNU ed (version @value{VERSION}, @value{UPDATED}). @menu * Overview:: Overview of the @command{ed} command * Introduction to line editing:: Getting started with GNU @command{ed} * Invoking ed:: Command-line interface * Argument syntax:: By convention, options start with a hyphen * Line addressing:: Specifying lines/ranges in the buffer * Regular expressions:: Patterns for selecting text * Commands:: Commands recognized by GNU @command{ed} * The 's' Command:: Substitute command * Limitations:: Intrinsic limits of GNU @command{ed} * Diagnostics:: GNU @command{ed} error handling * Problems:: Reporting bugs * GNU Free Documentation License:: How you can copy and share this manual @end menu @sp 1 @noindent Written by Andrew L. Moore, Franois Pinard, and Antonio Diaz Diaz @insertcopying @end ifnottex @node Overview @chapter Overview @uref{http://www.gnu.org/software/ed/ed.html,,GNU ed} is a line-oriented text editor. It is used to create, display, modify and otherwise manipulate text files, both interactively and via shell scripts. A restricted version of ed, red, can only edit files in the current directory and cannot execute shell commands. Ed is the 'standard' text editor in the sense that it is the original editor for Unix, and thus widely available. For most purposes, however, it is superseded by full-screen editors such as GNU Emacs or GNU Moe. GNU ed is based on the editor algorithm described in Brian W. Kernighan and P. J. Plauger's book "Software Tools in Pascal", Addison-Wesley, 1981. If invoked with a @var{file} argument, then a copy of @var{file} is read into the editor's buffer. Changes are made to this copy and not directly to @var{file} itself. Upon quitting @command{ed}, any changes not explicitly saved with a @samp{w} command are lost. In interactive mode, a non-existing @var{file} is reported but does not alter the exit status. Editing is done in two distinct modes: @dfn{command} and @dfn{input}. When first invoked, @command{ed} is in command mode. In this mode commands are read from the standard input and executed to manipulate the contents of the editor buffer. A typical command might look like: @example ,s/@var{old}/@var{new}/g @end example which replaces all occurences of the string @var{old} with @var{new}. When an input command, such as @samp{a} (append), @samp{i} (insert) or @samp{c} (change), is given, @command{ed} enters input mode. This is the primary means of adding text to a file. In this mode, no commands are available; instead, the standard input is written directly to the editor buffer. A @dfn{line} consists of the text up to and including a @key{newline} character. Input mode is terminated by entering a single period (@samp{.}) on a line. Any change made to the buffer sets the modified flag even if the change does not cause a change in the contents of the buffer; for example @samp{s/a/a/}. Reading an empty file (@samp{r @var{file}}) or terminating a command before it makes any change to the buffer (@samp{a@key{newline}.@key{newline}}) does not change the buffer, and therefore does not set the modified flag. All @command{ed} commands operate on whole lines or ranges of lines; e.g., the @samp{d} command deletes lines; the @samp{m} command moves lines, and so on. It is possible to modify only a portion of a line by means of replacement, as in the example above. However even here, the @samp{s} command is applied to whole lines at a time. In general, @command{ed} commands consist of zero or more line addresses, followed by a single character command and possibly additional parameters; i.e., commands have the structure: @example [@var{address}[,@var{address}]]@var{command}[@var{parameters}] @end example The @var{address}es indicate the line or range of lines to be affected by the command. If fewer addresses are given than the command accepts, then default addresses are supplied. @node Introduction to line editing @chapter Introduction to line editing @command{ed} was created, along with the Unix operating system, by Ken Thompson and Dennis Ritchie. It is the refinement of its more complex, programmable predecessor, @cite{QED}, to which Thompson and Ritchie had already added pattern matching capabilities (@pxref{Regular expressions}). For the purposes of this tutorial, a working knowledge of the Unix shell @command{sh} and the Unix file system is recommended, since @command{ed} is designed to interact closely with them. @ifnothtml (@xref{Top,GNU bash manual,,bash}, @end ifnothtml @ifhtml (See the @uref{http://www.gnu.org/software/bash/manual/,,bash manual} @end ifhtml for details about bash). The principal difference between line editors and display editors is that display editors provide instant feedback to user commands, whereas line editors require sometimes lengthy input before any effects are seen. The advantage of instant feedback, of course, is that if a mistake is made, it can be corrected immediately, before more damage is done. Editing in @command{ed} requires more strategy and forethought; but if you are up to the task, it can be quite efficient. Much of the @command{ed} command syntax is shared with other Unix utilities. As with the shell, @key{RETURN} (the carriage-return key) enters a line of input. So when we speak of "entering" a command or some text in @command{ed}, @key{RETURN} is implied at the end of each line. Prior to typing @key{RETURN}, corrections to the line may be made by typing either @key{BACKSPACE} to erase characters backwards, or @key{CONTROL}-u (i.e., hold the CONTROL key and type u) to erase the whole line. When @command{ed} first opens, it expects to be told what to do but doesn't prompt us like the shell. So let's begin by telling @command{ed} to do so with the @key{P} (@dfn{prompt}) command: @example $ ed P * @end example By default, @command{ed} uses asterisk (@samp{*}) as command prompt to avoid confusion with the shell command prompt (@samp{$}). We can run Unix shell (@command{sh}) commands from inside @command{ed} by prefixing them with @key{!} (exclamation mark, aka "bang"). For example: @example *!date Mon Jun 26 10:08:41 PDT 2006 ! *!for s in hello world; do echo $s; done hello world ! * @end example So far, this is no different from running commands in the Unix shell. But let's say we want to edit the output of a command, or save it to a file. First we must capture the command output to a temporary location called a @dfn{buffer} where @command{ed} can access it. This is done with @command{ed}'s @key{r} command (mnemonic: @dfn{read}): @example *r !cal -m 137 * @end example Here @command{ed} is telling us that it has just read 137 characters into the editor buffer - i.e., the output of the @command{cal} command, which prints a simple ASCII calendar. To display the buffer contents we issue the @key{p} (@dfn{print}) command (not to be confused with the prompt command, which is uppercase!). To indicate the range of lines in the buffer that should be printed, we prefix the command with @key{,} (comma) which is shorthand for "the whole buffer": @example *,p June 2006 Mo Tu We Th Fr Sa Su 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 * @end example Now let's write the buffer contents to a file named @samp{junk} with the @key{w} (@dfn{write}) command: @example *w junk 137 * @end example Need we say? It's good practice to frequently write the buffer contents, since unwritten changes to the buffer will be lost when we exit @command{ed}. The sample sessions below illustrate some basic concepts of line editing with @command{ed}. We begin by creating a file, @file{sonnet}, with some help from Shakespeare. As with the shell, all input to @command{ed} must be followed by a @key{newline} character. Commands beginning with @samp{#} are taken as comments and ignored. Input mode lines that begin with @samp{#} are just more input. @example $ ed # The 'a' command is for appending text to the editor buffer. a No more be grieved at that which thou hast done. Roses have thorns, and filvers foutians mud. Clouds and eclipses stain both moon and sun, And loathsome canker lives in sweetest bud. . # Entering a single period on a line returns @command{ed} to command mode. # Now write the buffer to the file @file{sonnet} and quit: w sonnet 183 # @command{ed} reports the number of characters written. q $ ls -l total 2 -rw-rw-r-- 1 alm 183 Nov 10 01:16 sonnet $ @end example In the next example, some typos are corrected in the file @file{sonnet}. @example $ ed sonnet 183 # Begin by printing the buffer to the terminal with the @samp{p} command. # The ',' means "all lines". ,p No more be grieved at that which thou hast done. Roses have thorns, and filvers foutians mud. Clouds and eclipses stain both moon and sun, And loathsome canker lives in sweetest bud. # Select line 2 for editing. 2 Roses have thorns, and filvers foutians mud. # Use the substitute command, @samp{s}, to replace 'filvers' with 'silver', # and print the result. s/filvers/silver/p Roses have thorns, and silver foutians mud. # And correct the spelling of 'fountains'. s/utia/untai/p Roses have thorns, and silver fountains mud. w sonnet 183 q $ @end example Since @command{ed} is line-oriented, we have to tell it which line, or range of lines we want to edit. In the example above, we do this by specifying the line's number, or sequence in the buffer. Alternatively, we could have specified a unique string in the line, e.g., @samp{/filvers/}, where the @samp{/}s delimit the string in question. Subsequent commands affect only the selected line, a.k.a. the @dfn{current} line. Portions of that line are then replaced with the substitute command, whose syntax is @samp{s/@var{old}/@var{new}/}. Although @command{ed} accepts only one command per line, the print command @samp{p} is an exception, and may be appended to the end of most commands. In the next example, a title is added to our sonnet. @example $ ed sonnet 183 a Sonnet #50 . ,p No more be grieved at that which thou hast done. Roses have thorns, and silver fountains mud. Clouds and eclipses stain both moon and sun, And loathsome canker lives in sweetest bud. Sonnet #50 # The title got appended to the end; we should have used '0a' # to append "before the first line". # Move the title to its proper place. 5m0p Sonnet #50 # The title is now the first line, and the current address has been # set to the address of this line as well. ,p Sonnet #50 No more be grieved at that which thou hast done. Roses have thorns, and silver fountains mud. Clouds and eclipses stain both moon and sun, And loathsome canker lives in sweetest bud. wq sonnet 195 $ @end example When @command{ed} opens a file, the current address is initially set to the address of the last line of that file. Similarly, the move command @samp{m} sets the current address to the address of the last line moved. Related programs or routines are @command{vi}(1), @command{sed}(1), @command{regex}(3), @command{sh}(1). Relevant documents are: @quotation Unix User's Manual Supplementary Documents: 12 --- 13 @end quotation @quotation B. W. Kernighan and P. J. Plauger: "Software Tools in Pascal", Addison-Wesley, 1981. @end quotation @node Invoking ed @chapter Invoking ed The format for running @command{ed} is: @example ed [@var{options}] [[+@var{line}] @var{file}] red [@var{options}] [[+@var{line}] @var{file}] ed [@var{options}] [[+@var{line}] '!@var{command} [@var{arguments}]'] @end example @var{file} specifies the name of a file to read. If @var{file} is prefixed with a bang (!), then it is interpreted as a shell command. In this case, what is read is the standard output of @var{file} executed via @command{sh}. To read a file whose name begins with a bang (or a hyphen), prefix the name with @file{./}. The default filename is set to @var{file} only if it is not prefixed with a bang. The file name may be preceded by @samp{+@var{line}} to set the current line to the line number specified. If @var{line} exceeds the number of lines in the file, the current line is set to the last line. The file name may be preceded by @samp{+/@var{re}} to set the current line to the first line matching the regular expression @var{re}, or by @samp{+?@var{re}} to set the current line to the last line matching @var{re}. If @var{re} does not match any line, ed fails immediately with exit status 1 unless the option @option{-l} is specified and standard input is not a regular file. @noindent @command{ed} supports the following options: @xref{Argument syntax}. @table @code @item -h @itemx --help Print an informative help message describing the options and exit. @item -V @itemx --version Print the version number of @command{ed} on the standard output and exit. This version number should be included in all bug reports. @item -E @itemx --extended-regexp Use extended regular expressions instead of the basic regular expressions mandated by POSIX. @item -G @itemx --traditional Forces backwards compatibility. This affects the behavior of the @command{ed} commands @samp{G}, @samp{V}, @samp{f}, @samp{l}, @samp{m}, @samp{t}, and @samp{!!}. If the default behavior of these commands does not seem familiar, then try invoking @command{ed} with this switch. @item -l @itemx --loose-exit-status Don't exit with bad status if a command happens to "fail" (for example if a substitution command finds nothing to replace). This can be useful when @command{ed} is invoked as the editor for crontab. @item -p @var{string} @itemx --prompt=@var{string} Specifies a command prompt string and turns prompting on. Showing the prompt string may be toggled on and off with the @samp{P} command. @item -q @itemx --quiet @itemx --silent Suppress diagnostic messages written to standard error. @item -r @itemx --restricted Run in restricted mode. This mode disables editing of files out of the current directory and execution of shell commands. @item -s @itemx --script Suppress the printing of byte counts by @samp{e}, @samp{E}, @samp{r}, and @samp{w} commands, and the @samp{!} prompt after a @samp{!} command. Suppress also the messages "Newline inserted" and "Newline appended". This option does not suppress diagnostic messages written to standard error (see @option{-q} above). @option{-s} may be useful if @command{ed}'s standard input is from a script. @item -v @itemx --verbose Turn help mode on; print a help message explaining the reason for each @samp{?} notification. This may be toggled on and off with the @samp{H} command. Use this option to aid in debugging ed scripts. @item --strip-trailing-cr Strip the carriage returns at the end of text lines in DOS files. CRs are removed only from the CR/LF (carriage return/line feed) pair ending the line. CRs at other positions in the line, including a CR finishing an unterminated line, are not removed. The CRs are not restored when saving the buffer to a file. @item --unsafe-names @command{ed} rejects file names containing control characters 1 to 31 unless they are allowed with this option. @end table Exit status: 0 for a normal exit, 1 for environmental problems (invalid command-line options, memory exhausted, command failed, etc), 2 for problems with the input file (file not found, buffer modified, I/O errors), 3 for an internal consistency error (e.g., bug) which caused ed to panic. @node Argument syntax @chapter Syntax of command-line arguments @cindex argument syntax POSIX recommends these conventions for command-line arguments. @itemize @bullet @item A command-line argument is an option if it begins with a hyphen (@samp{-}). @item Option names are single alphanumeric characters. @item Certain options require an argument. @item An option and its argument may or may not appear as separate tokens. (In other words, the whitespace separating them is optional). Thus, @w{@option{-o foo}} and @option{-ofoo} are equivalent. @item One or more options without arguments, followed by at most one option that takes an argument, may follow a hyphen in a single token. Thus, @option{-abc} is equivalent to @w{@option{-a -b -c}}. @item Options typically precede other non-option arguments. @item The argument @samp{--} terminates all options; any following arguments are treated as non-option arguments, even if they begin with a hyphen. @item A token consisting of a single hyphen character is interpreted as an ordinary non-option argument. By convention, it is used to specify standard input, standard output, or a file named @samp{-}. @end itemize @noindent GNU adds @dfn{long options} to these conventions: @itemize @bullet @item A long option consists of two hyphens (@samp{--}) followed by a name made of alphanumeric characters and hyphens. Option names are typically one to three words long, with hyphens to separate words. Abbreviations can be used for the long option names as long as the abbreviations are unique. @item A long option and its argument may or may not appear as separate tokens. In the latter case they must be separated by an equal sign @samp{=}. Thus, @w{@option{--foo bar}} and @option{--foo=bar} are equivalent. @end itemize @node Line addressing @chapter Line addressing An address represents the number of a line in the buffer. @command{ed} maintains a @dfn{current address} which is typically supplied to commands as the default address when none is specified. When a file is first read, the current address is set to the address of the last line of the file. In general, the current address is set to the address of the last line affected by a command. One exception to the rule that addresses represent line numbers is the address @samp{0} (zero). This means "at the beginning of the buffer", and is valid wherever it makes sense. An address range is two addresses separated either by a comma (@samp{,}) or a semicolon (@samp{;}). The value of the first address in a range cannot exceed the value of the second. In a semicolon-delimited range, the current address (@samp{.}) is set to the first address before the second address is calculated. This feature can be used to set the starting line for searches if the second address contains a regular expression. The address @samp{0} (zero) is valid as a starting point so that @samp{0;/@var{re}/} can match @var{re} in the first line of the buffer. Addresses can be omitted on either side of the comma or semicolon separator. If only the first address is given in a range, then the second address is set to the given address. If only the second address is given, the resulting address pairs are @samp{1,addr} and @samp{.;addr} respectively. If a @var{n}-tuple of addresses is given where @w{@var{n} > 2}, then the corresponding range is determined by the last two addresses in the @var{n}-tuple. If only one address is expected, then the last address is used. It is an error to give any number of addresses to a command that requires zero addresses. A line address is constructed as follows: @table @code @item . The current line (address) in the buffer. @item $ The last line in the buffer. @item @var{n} The @var{n}th line in the buffer, where @var{n} is a number in the range @samp{0,$}. @item +@var{n} The @var{n}th next line, where @var{n} is a non-negative number. @item -@var{n} The @var{n}th previous line, where @var{n} is a non-negative number. @item + The next line. This is equivalent to @samp{+1} and may be repeated with cumulative effect. @item - The previous line. This is equivalent to @samp{-1} and may be repeated with cumulative effect. @item , The first through last lines in the buffer. This is equivalent to the address range @samp{1,$}. @item ; The current through last lines in the buffer. This is equivalent to the address range @samp{.;$}. @item /@var{re}/[I] The next line containing the regular expression @var{re}. The search wraps to the beginning of the buffer and continues down to the current line, if necessary. The suffix @samp{I} is a GNU extension which makes @command{ed} match @var{re} in a case-insensitive manner. @item ?@var{re}?[I] The previous line containing the regular expression @var{re}. The search wraps to the end of the buffer and continues up to the current line, if necessary. The suffix @samp{I} is a GNU extension which makes @command{ed} match @var{re} in a case-insensitive manner. @item 'x The apostrophe-x character pair addresses the line previously marked by a @samp{k} (mark) command, where @samp{x} is a lower case letter from the portable character set @samp{[a-z]}. @end table Addresses can be followed by one or more address offsets, optionally separated by whitespace. Offsets are constructed as follows: @itemize @bullet @item @samp{+} or @samp{-} followed by a number adds or subtracts the indicated number of lines to or from the address. @item @samp{+} or @samp{-} not followed by a number adds or subtracts 1 to or from the address. @item A number adds the indicated number of lines to the address. @end itemize It is not an error if an intermediate address value is negative or greater than the address of the last line in the buffer. It is an error if the final address value is negative or greater than the address of the last line in the buffer. It is an error if a search for a regular expression fails to find a matching line. @node Regular expressions @chapter Regular expressions Regular expressions are patterns used in selecting text. For example, the @command{ed} command @example g/@var{string}/ @end example @noindent prints all lines containing @var{string}. Regular expressions are also used by the @samp{s} command for selecting old text to be replaced with new text. In addition to specifying string literals, regular expressions can represent classes of strings. Strings thus represented are said to be matched by the corresponding regular expression. If it is possible for a regular expression to match several strings in a line, then the left-most match is the one selected. If the regular expression permits a variable number of matching characters, the longest sequence starting at that point is matched. An empty regular expression is equivalent to the last regular expression processed. Therefore @samp{/@var{re}/s//@var{replacement}/} replaces @var{re} with @var{replacement}. As a GNU extension, a regular expression /@var{re}/ may be followed by the suffix @samp{I} which makes @command{ed} match @var{re} in a case-insensitive manner. Note that the suffix is evaluated when the regular expression is compiled, thus it is invalid to specify it together with the empty regular expression. The following symbols are used in constructing regular expressions using POSIX basic regular expression syntax: @table @code @item @var{c} Any character @var{c} not listed below, including @samp{@{}, @samp{@}}, @samp{(}, @samp{)}, @samp{<}, and @samp{>}, matches itself. @item \@var{c} Any backslash-escaped character @var{c}, other than @samp{@{}, @samp{@}}, @samp{(}, @samp{)}, @samp{<}, @samp{>}, @samp{b}, @samp{B}, @samp{w}, @samp{W}, @samp{+}, and @samp{?}, matches itself. @item . Matches any single character. @item [@var{char-class}] Matches any single character in @var{char-class}. To include a @samp{]} in @var{char-class}, it must be the first character. A range of characters may be specified by separating the end characters of the range with a @samp{-}, e.g., @samp{a-z} specifies the lower case characters. The following literal expressions can also be used in @var{char-class} to specify sets of characters: @example [:alnum:] [:cntrl:] [:lower:] [:space:] [:alpha:] [:digit:] [:print:] [:upper:] [:blank:] [:graph:] [:punct:] [:xdigit:] @end example If @samp{-} appears as the first or last character of @var{char-class}, then it matches itself. All other characters in @var{char-class} match themselves. Patterns in @var{char-class} of the form: @example [.@var{col-elm}.] [=@var{col-elm}=] @end example @noindent where @var{col-elm} is a @dfn{collating element} are interpreted according to @samp{locale}(5). See @samp{regex}(7) for an explanation of these constructs. @item [^@var{char-class}] Matches any single character, other than newline, not in @var{char-class}. @var{char-class} is defined as above. @item ^ If @samp{^} is the first character of a regular expression, then it anchors the regular expression to the beginning of a line. Otherwise, it matches itself. @item $ If @samp{$} is the last character of a regular expression, it anchors the regular expression to the end of a line. Otherwise, it matches itself. @item \(@var{re}\) Defines a (possibly empty) subexpression @var{re}. Subexpressions may be nested. A subsequent backreference of the form @samp{\@var{n}}, where @var{n} is a number in the range [1,9], expands to the text matched by the @var{n}th subexpression. For example, the regular expression @samp{\(a.c\)\1} matches the string @samp{abcabc}, but not @samp{abcadc}. Subexpressions are ordered relative to their left delimiter. @item * Matches zero or more repetitions of the regular expression immediately preceding it. The regular expression can be either a single character regular expression or a subexpression. If @samp{*} is the first character of a regular expression or subexpression, then it matches itself. The @samp{*} operator sometimes yields unexpected results. For example, the regular expression @samp{b*} matches the beginning of the string @samp{abbb}, as opposed to the substring @samp{bbb}, since an empty string is the only left-most match. @item \@{@var{n},@var{m}\@} @itemx \@{@var{n},\@} @itemx \@{@var{n}\@} Matches the single character regular expression or subexpression immediately preceding it at least @var{n} and at most @var{m} times. If @var{m} is omitted, then it matches at least @var{n} times. If the comma is also omitted, then it matches exactly @var{n} times. If any of these forms occurs first in a regular expression or subexpression, then it is interpreted literally (i.e., the regular expression @samp{\@{2\@}} matches the string @samp{@{2@}}, and so on). @end table The following extensions to basic regular expression operators are preceded by a backslash @samp{\} to distinguish them from traditional @command{ed} syntax. They may be unavailable depending on the particular regex implementation in your system. @table @code @item \< @itemx \> Anchors the single character regular expression or subexpression immediately following it to the beginning (in the case of @samp{\<}) or ending (in the case of @samp{\>}) of a @dfn{word}, i.e., in ASCII, a maximal string of alphanumeric characters, including the underscore (_). @item \` @itemx \' Unconditionally matches the beginning @samp{\`} or ending @samp{\'} of a line. @item \? Optionally matches the single character regular expression or subexpression immediately preceding it. For example, the regular expression @samp{a[bd]\?c} matches the strings @samp{abc}, @samp{adc} and @samp{ac}. If @samp{\?} occurs at the beginning of a regular expressions or subexpression, then it matches a literal @samp{?}. @item \+ Matches the single character regular expression or subexpression immediately preceding it one or more times. So the regular expression @samp{a\+} is shorthand for @samp{aa*}. If @samp{\+} occurs at the beginning of a regular expression or subexpression, then it matches a literal @samp{+}. @item \b Matches the beginning or ending (empty string) of a word. Thus the regular expression @samp{\bhello\b} is equivalent to @samp{\}. However, @samp{\b\b} is a valid regular expression whereas @samp{\<\>} is not. @item \B Matches (an empty string) inside a word. @item \w Matches any word-constituent character (letters, digits, and the underscore). @item \W Matches any character that is not a word-constituent. @end table @node Commands @chapter Commands All @command{ed} commands are single characters, though some require additional parameters. If a command's parameters extend over several lines, then each line except for the last must be terminated with a backslash (@samp{\}). @anchor{print suffixes} In general, at most one command is allowed per line. However, most commands accept a print suffix, which is any of @samp{p} (print), @samp{l} (list), or @samp{n} (enumerate), to print the last line affected by the command. It is not portable to give more than one print suffix, but @command{ed} allows any combination of non-repeated print suffixes and combines their effects. If any suffix letter is given, it must immediately follow the command. The @samp{e}, @samp{E}, @samp{f}, @samp{r}, and @samp{w} commands take an optional @var{file} parameter, separated from the command letter by one or more whitespace characters. An interrupt (typically @key{Control-C}) has the effect of aborting the current command and returning the editor to command mode. @command{ed} recognizes the following commands. The commands are shown together with the default address or address range supplied if none is specified (in parenthesis). @table @code @item (.)a Appends text to the buffer after the addressed line. The address @samp{0} (zero) is valid for this command; it places the entered text at the beginning of the buffer. Text is entered in input mode. The current address is set to the address of the last line entered or, if there were none, to the addressed line. @item (.,.)c Changes lines in the buffer. The addressed lines are deleted from the buffer, and text is inserted in their place. Text is entered in input mode. The current address is set to the address of the last line entered or, if there were none, to the new address of the line after the last line deleted; if the lines deleted were originally at the end of the buffer, the current address is set to the address of the new last line; if no lines remain in the buffer, the current address is set to zero. The lines deleted are copied to the cut buffer. @item (.,.)d Deletes the addressed lines from the buffer. The current address is set to the new address of the line after the last line deleted; if the lines deleted were originally at the end of the buffer, the current address is set to the address of the new last line; if no lines remain in the buffer, the current address is set to zero. The lines deleted are copied to the cut buffer. @item e @var{file} Edits @var{file}, and sets the default filename. If @var{file} is not specified, then the default filename is used. Any lines in the buffer are deleted before the new file is read. If @var{file} does not exist, a warning is printed in place of a byte count and the resulting buffer is left empty. The current address is set to the address of the last line in the buffer. If @var{file} is prefixed with a bang (!), then it is interpreted as a shell command whose output is to be read, (@pxref{shell escape command} @samp{!} below). In this case the default filename is unchanged. To edit a file whose name begins with a bang, prefix the name with @file{./}. A warning is printed if the buffer modified flag is set. If another @samp{e} or @samp{q} command is given with no intervening error or buffer-modifying command, it is executed without warning, and any changes to the buffer are lost. @item E @var{file} Edits @var{file} unconditionally. This is similar to the @samp{e} command, except that unwritten changes are discarded without warning. @item f @var{file} Sets the default filename to @var{file}, whether or not @var{file} names an existing file. If @var{file} is not specified, then the default filename is printed. Tilde expansion is performed on @var{file}; if @var{file} starts with @file{~/}, the @file{~} is expanded to specify your home directory @file{$HOME}. @item (1,$)g/@var{re}/[I]@var{command-list} Global command. The global command makes two passes over the file. On the first pass, all the addressed lines matching a regular expression @var{re} are marked. The suffix @samp{I} is a GNU extension which makes @command{ed} match @var{re} in a case-insensitive manner. Then, going sequentially from the beginning of the file to the end of the file, the given @var{command-list} is executed for each marked line, with the current address set to the address of that line. Any line modified by the @var{command-list} is unmarked. The final value of the current address is the value assigned by the last command in the last @var{command-list} executed. If there were no matching lines, the current address is unchanged. The execution of @var{command-list} stops on the first error. The first command of @var{command-list} must appear on the same line as the @samp{g} command. The other commands of @var{command-list} must appear on separate lines. All lines of a multi-line @var{command-list} except the last line must be terminated with a backslash (@samp{\}). Any commands are allowed, except for @samp{g}, @samp{G}, @samp{v}, and @samp{V}. The @samp{.} terminating the input mode of commands @samp{a}, @samp{c}, and @samp{i} can be omitted if it would be the last line of @var{command-list}. By default, a newline alone in @var{command-list} is equivalent to a @samp{p} command. If @command{ed} is invoked with the command-line option @option{-G}, then a newline in @var{command-list} is equivalent to a @samp{.+1p} command. @item (1,$)G/@var{re}/[I] Interactive global command. Interactively edits the addressed lines matching a regular expression @var{re}. The suffix @samp{I} is a GNU extension which makes @command{ed} match @var{re} in a case-insensitive manner. For each matching line, the line is printed, the current address is set, and the user is prompted to enter a @var{command-list}. The final value of the current address is the value assigned by the last command executed. If there were no matching lines, the current address is unchanged. The format of @var{command-list} is the same as that of the @samp{g} command. A newline alone acts as an empty command list. A single @samp{&} repeats the last non-empty command list. @item h Prints a help message explaining the reason for the most recent @samp{?} notification. @item H Toggles the printing of help messages (see the @samp{h} command above). If the help mode is being turned on, also prints the help message corresponding to the most recent @samp{?} notification. By default, help messages are not printed. @item (.)i Inserts text in the buffer before the addressed line. The address @samp{0} (zero) is valid for this command; it places the entered text at the beginning of the buffer. Text is entered in input mode. The current address is set to the address of the last line entered or, if there were none, to the addressed line. @item (.,.+1)j Joins the addressed lines, replacing them by a single line containing their joined text. If only one address is given, this command does nothing. If lines are joined, the lines replaced are copied to the cut buffer and the current address is set to the address of the joined line. Else, the current address is unchanged. @item (.)kx Marks a line with a lower case letter @samp{x}. The line can then be addressed as @samp{'x} (i.e., a single quote followed by @samp{x}) in subsequent commands. The mark is not cleared until the line is deleted or otherwise modified. The current address is unchanged. @item (.,.)l List command. Prints the addressed lines unambiguously. The end of each line is marked with a @samp{$}, and every @samp{$} character within the text is printed with a preceding backslash. Special characters are printed as escape sequences. The current address is set to the address of the last line printed. @item (.,.)m(.) Moves lines in the buffer. The addressed lines are moved to after the right-hand destination address. The destination address @samp{0} (zero) is valid for this command; it moves the addressed lines to the beginning of the buffer. It is an error if the destination address falls within the range of lines to be moved. The current address is set to the new address of the last line moved. @item (.,.)n Number command. Prints the addressed lines, preceding each line by its line number and a @key{tab}. The current address is set to the address of the last line printed. @item (.,.)p Prints the addressed lines. The current address is set to the address of the last line printed. @item P Toggles the command prompt on and off. Unless a prompt string is specified with the command-line option @option{-p}, the command prompt is by default turned off. The default prompt string is an asterisk (@samp{*}). @item q Quits @command{ed}. A warning is printed if the buffer modified flag is set. If another @samp{e} or @samp{q} command is given with no intervening error or buffer-modifying command, it is executed without warning, and any changes to the buffer are lost. @item Q Quits @command{ed} unconditionally. This is similar to the @samp{q} command, except that unwritten changes are discarded without warning. @item ($)r @var{file} Reads @var{file} and appends it after the addressed line. If @var{file} is not specified, then the default filename is used. If there is no default filename prior to the command, then the default filename is set to @var{file}. Otherwise, the default filename is unchanged. The address @samp{0} (zero) is valid for this command; it reads the file at the beginning of the buffer. The current address is set to the address of the last line read or, if there were none, to the addressed line. If @var{file} is prefixed with a bang (!), then it is interpreted as a shell command whose output is to be read, (@pxref{shell escape command} @samp{!} below). In this case the default filename is unchanged. To read a file whose name begins with a bang, prefix the name with @file{./}. @item (.,.)t(.) Copies (i.e., transfers) the addressed lines to after the right-hand destination address. If the destination address is @samp{0} (zero), the lines are copied at the beginning of the buffer. The current address is set to the address of the last line copied. @item u Undoes the effect of the last command that modified anything in the buffer and restores the current address to what it was before the command. The global commands @samp{g}, @samp{G}, @samp{v}, and @samp{V} are treated as a single command by undo. @samp{u} is its own inverse; it can undo only the last command. @item (1,$)v/@var{re}/[I]@var{command-list} This is similar to the @samp{g} command except that it applies @var{command-list} to each of the addressed lines not matching the regular expression @var{re}. @item (1,$)V/@var{re}/[I] This is similar to the @samp{G} command except that it interactively edits the addressed lines not matching the regular expression @var{re}. @item (1,$)w @var{file} Writes the addressed lines to @var{file}. Any previous contents of @var{file} are lost without warning. If there is no default filename, then the default filename is set to @var{file}, otherwise it is unchanged. If no filename is specified, then the default filename is used. If the whole buffer is written, then the buffer modified flag is cleared. The current address is unchanged. If @var{file} is prefixed with a bang (!), then it is interpreted as a shell command and the addressed lines are written to its standard input, (@pxref{shell escape command} @samp{!} below). In this case the default filename is unchanged. Writing the buffer to a shell command does not clear the buffer modified flag. To write to a file whose name begins with a bang, prefix the name with @file{./}. @item (1,$)wq @var{file} Writes the addressed lines to @var{file}, and then executes a @samp{q} command. @item (1,$)W @var{file} Appends the addressed lines to the end of @var{file}. This is similar to the @samp{w} command, except that the previous contents of @var{file} are not clobbered. The current address is unchanged. @item (.)x Copies (puts) the contents of the cut buffer to after the addressed line. The current address is set to the address of the last line copied. @item (.,.)y Copies (yanks) the addressed lines to the cut buffer. The cut buffer is overwritten by subsequent @samp{c}, @samp{d}, @samp{j}, @samp{s}, or @samp{y} commands. The current address is unchanged. @item (.+1)z@var{n} Scroll. Prints @var{n} lines at a time starting at addressed line, and sets window size to @var{n}. If @var{n} is not specified, then the current window size is used. Window size defaults to screen size minus two lines, or to 22 if screen size can't be determined. The environment variable @var{LINES} can be used to set the initial window size. @var{LINES} and @var{n} are not limited by screen size. The current address is set to the address of the last line printed. @anchor{shell escape command} @item !@var{command} Shell escape command. Executes @var{command} via @command{sh}. If the first character of @var{command} is @samp{!}, then it is replaced by the text of the previous @samp{!@var{command}}. Thus, @samp{!!} repeats the previous @samp{!@var{command}}. @command{ed} does not process @var{command} for backslash (@samp{\}) escapes. However, each unescaped @samp{%} is replaced with the default filename, and the backslash is removed from each escaped @samp{%}. When the shell returns from execution, a @samp{!} is printed to the standard output. The current address is unchanged. The effect of ex(1) style @samp{!} filtering can be achieved with the following sequence of commands: @example @var{addr1},@var{addr2}w !@var{command} > tmp.txt 2>&1 @var{addr2}r tmp.txt @var{addr1},@var{addr2}d !rm tmp.txt @end example @item (.,.)# Begins a comment; the rest of the line, up to a newline, is ignored. If a line address followed by a semicolon is given, then the current address is set to that address. Otherwise, the current address is unchanged. @item ($)= Prints the line number of the addressed line. The current address is unchanged. @item (.+1)@key{newline} Null command. An address alone prints the addressed line. A @key{newline} alone is equivalent to @samp{+1p}. The current address is set to the address of the printed line. @end table @node The 's' Command @chapter Substitute command The substitute command @samp{s} replaces text in the addressed lines matching a regular expression @var{re} with @var{replacement}. By default, only the first match in each line is replaced. The syntax of the @samp{s} command is: @example (.,.)s/@var{re}/@var{replacement}/[@var{suffixes}] @end example The @samp{s} command accepts any combination of the following optional suffixes: @table @code @item g @samp{global}: replace every match in the line, not just the first. @item @var{count} A positive number causes only the @var{count}th match to be replaced. @samp{g} and @samp{@var{count}} can't be specified in the same command. @item l @itemx n @itemx p The usual print suffixes. @xref{print suffixes}. @item I @itemx i The suffix @samp{I} is a GNU extension which makes @command{ed} match @var{re} in a case-insensitive manner. @end table It is an error if no substitutions are performed on any of the addressed lines. The current address is set to the address of the last line on which a substitution occurred. If a line is split, a substitution is considered to have occurred on each of the new lines. If no substitution is performed, the current address is unchanged. The last line modified is copied to the cut buffer. @var{re} and @var{replacement} may be delimited by any character other than @key{space}, @key{newline}, and the characters used by the form of the @samp{s} command shown below. If the last delimiter is omitted, then the last line affected is printed as if the print suffix @samp{p} were specified. The last delimiter can't be omitted if the @samp{s} command is part of a @samp{g} or @samp{v} @var{command-list} and is not the last command in the list, because the meaning of the following escaped newline would become ambiguous. An unescaped @samp{&} in @var{replacement} is replaced by the currently matched text. The character sequence @samp{\@var{m}} where @var{m} is a number in the range [1,9], is replaced by the @var{m}th backreference expression of the matched text. If the corresponding backreference expression does not match, then the character sequence @samp{\@var{m}} is replaced by the empty string. If @var{replacement} consists of a single @samp{%}, then @var{replacement} from the last substitution is used. A line can be split by including a newline escaped with a backslash (@samp{\}) in @var{replacement}. Each backslash in @var{replacement} removes the special meaning (if any) of the following character. @command{ed} can repeat the last substitution using the following alternative syntax for the @samp{s} command: @example (.,.)s[@var{suffixes}] @end example This form of the @samp{s} command accepts the suffixes @samp{g} and @samp{@var{count}} described above, and any combination of the suffixes @samp{p} and @samp{r}. The suffix @samp{g} toggles the global suffix of the last substitution and resets @var{count} to 1. The suffix @samp{p} toggles the print suffixes of the last substitution. The suffix @samp{r} causes the @var{re} of the last search to be used instead of the @var{re} of the last substitution (if the search happened after the substitution). @node Limitations @chapter Limitations If the terminal hangs up and the buffer is modified and not empty, @command{ed} attempts to write the buffer to the file @file{ed.hup} or, if this fails, to @file{$HOME/ed.hup}. If a text (non-binary) file is not terminated by a newline character, then @command{ed} appends one on reading/writing it. In the case of a binary file, @command{ed} does not append a newline on reading/writing. A binary file is one containing at least one ASCII NUL character. If the last line has been modified, reading an empty file, for example @file{/dev/null}, prior to writing prevents appending a newline to a binary file. In order to keep track of the text lines in the buffer, @command{ed} uses a doubly linked list of structures containing the position and size of each line. This results in a per line overhead of @w{2 @samp{pointer}s}, @w{1 @samp{long int}}, and @w{1 @samp{int}}. The maximum line length is @w{INT_MAX - 1} bytes. The maximum number of lines is @w{INT_MAX - 2} lines. @node Diagnostics @chapter Diagnostics @command{ed} prints two kinds of mesages: diagnostic messages (errors and warnings) written to standard error and help messages written to standard output. Diagnostic messages may be suppresed with option @option{-q}. Help messages may be enabled with option @option{-v} and may be toggled with command @samp{H}. @command{ed} warns the first time that a command modifies a buffer loaded from a read-only file. When an error occurs, if @command{ed}'s standard input is a regular file or here document, then it exits, otherwise it prints a @samp{?} and returns to command mode. An explanation of the last error can be printed with the @samp{h} (help) command. If the @samp{u} (undo) command occurs in a global command list, then the command list is executed only once. @node Problems @chapter Reporting bugs There are probably bugs in @command{ed}. There are certainly errors and omissions in this manual. If you report them, they will get fixed. If you don't, no one will ever know about them and they will remain unfixed for all eternity, if not longer. If you find a bug in @command{ed}, please send electronic mail to @email{bug-ed@@gnu.org}. Include the version number, which you can find by running @w{@samp{ed --version}}. @node GNU Free Documentation License @chapter GNU Free Documentation License @include fdl.texi @bye ed_1.21.1/global.c0000644000175000017500000000606314767570754013537 0ustar dogslegdogsleg/* global.c: global command routines for the ed line editor */ /* GNU ed - The GNU line editor. Copyright (C) 1993, 1994 Andrew L. Moore, Talke Studio Copyright (C) 2006-2025 Antonio Diaz Diaz. 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "ed.h" /* list of lines active in a global command */ static const line_node **active_list = 0; static int active_size = 0; /* size (in bytes) of active_list */ static int active_len = 0; /* number of lines in active_list */ static int active_idx = 0; /* active_list index ( non-decreasing ) */ static int active_idxm = 0; /* active_list index ( modulo active_len ) */ /* clear the global-active list */ void clear_active_list( void ) { disable_interrupts(); if( active_list ) free( active_list ); active_list = 0; active_size = active_len = active_idx = active_idxm = 0; enable_interrupts(); } /* return the next global-active line node */ const line_node * next_active_node( void ) { while( active_idx < active_len && !active_list[active_idx] ) ++active_idx; return ( active_idx < active_len ) ? active_list[active_idx++] : 0; } /* add a line node to the global-active list */ bool set_active_node( const line_node * const lp ) { const unsigned min_size = ( active_len + 1 ) * sizeof (line_node **); if( (unsigned)active_size < min_size ) { if( min_size >= INT_MAX ) { set_error_msg( "Too many matching lines" ); return false; } const int new_size = ( ( min_size < 512 ) ? 512 : ( min_size >= INT_MAX / 2 ) ? INT_MAX - 1 : ( min_size / 512 ) * 1024 ); void * new_buf = 0; disable_interrupts(); if( active_list ) new_buf = realloc( active_list, new_size ); else new_buf = malloc( new_size ); if( !new_buf ) { show_strerror( 0, errno ); set_error_msg( mem_msg ); enable_interrupts(); return false; } active_size = new_size; active_list = (const line_node **)new_buf; enable_interrupts(); } active_list[active_len++] = lp; return true; } /* remove a range of lines from the global-active list */ void unset_active_nodes( const line_node * bp, const line_node * const ep ) { while( bp != ep ) { int i; for( i = 0; i < active_len; ++i ) { if( ++active_idxm >= active_len ) active_idxm = 0; if( active_list[active_idxm] == bp ) { active_list[active_idxm] = 0; break; } } bp = bp->q_forw; } } ed_1.21.1/INSTALL0000644000175000017500000000425114736572031013143 0ustar dogslegdogslegRequirements ------------ You will need a C99 compiler and a C library compatible with POSIX. (gcc 3.3.6 or newer is recommended). I use gcc 6.1.0 and 3.3.6, but the code should compile with any standards compliant compiler. Gcc is available at http://gcc.gnu.org Lzip is available at http://www.nongnu.org/lzip/lzip.html Procedure --------- 1. Unpack the archive if you have not done so already: tar -xf ed[version].tar.lz or lzip -cd ed[version].tar.lz | tar -xf - This creates the directory ./ed[version] containing the source code extracted from the archive. 2. Change to ed directory and run configure. (Try 'configure --help' for usage instructions). cd ed[version] ./configure If you choose a C standard, enable the POSIX features explicitly: ./configure CFLAGS+='--std=c99 -D_POSIX_C_SOURCE=2' 3. Run make. make 4. Optionally, type 'make check' to run the tests that come with ed. 5. Type 'make install' to install the program and any data files and documentation. You need root privileges to install into a prefix owned by root. Or type 'make install-compress', which additionally compresses the info manual and the man page after installation. (Installing compressed docs may become the default in the future). You can install only the program, the info manual, or the man page by typing 'make install-bin', 'make install-info', or 'make install-man' respectively. Another way ----------- You can also compile ed into a separate directory. To do this, you must use a version of 'make' that supports the variable 'VPATH', such as GNU 'make'. 'cd' to the directory where you want the object files and executables to go and run the 'configure' script. 'configure' automatically checks for the source code in '.', in '..', and in the directory that 'configure' is in. 'configure' recognizes the option '--srcdir=DIR' to control where to look for the source code. Usually 'configure' can determine that directory automatically. After running 'configure', you can run 'make' and 'make install' as explained above. Copyright (C) 2006-2025 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute, and modify it. ed_1.21.1/regex.c0000644000175000017500000003504714767571623013411 0ustar dogslegdogsleg/* regex.c: regular expression interface routines for the ed line editor. */ /* GNU ed - The GNU line editor. Copyright (C) 1993, 1994 Andrew L. Moore, Talke Studio Copyright (C) 2006-2025 Antonio Diaz Diaz. 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "ed.h" static const char * const inv_i_suf = "Suffix 'I' not allowed on empty regexp"; static const char * const inv_pat_del = "Invalid pattern delimiter"; static const char * const mis_pat_del = "Missing pattern delimiter"; static const char * const no_match = "No match"; static const char * const no_prev_pat = "No previous pattern"; static regex_t * last_regexp = 0; /* pointer to last regex found */ static regex_t * subst_regexp = 0; /* regex of last substitution */ static char * rbuf = 0; /* replacement buffer */ static int rbufsz = 0; /* replacement buffer size */ static int rlen = 0; /* replacement length */ bool subst_regex( void ) { return subst_regexp != 0; } /* translate characters in a string */ static void translit_text( char * p, int len, const char from, const char to ) { while( --len >= 0 ) { if( *p == from ) *p = to; ++p; } } /* overwrite newlines with ASCII NULs */ static void newline_to_nul( char * const s, const int len ) { translit_text( s, len, '\n', '\0' ); } /* overwrite ASCII NULs with newlines */ static void nul_to_newline( char * const s, const int len ) { translit_text( s, len, '\0', '\n' ); } bool islf_or_nul( const unsigned char ch ) { return ch == '\n' || ch == 0; } /* expand and skip a POSIX character class */ static const char * parse_char_class( const char * p ) { char c, d; if( *p == '^' ) ++p; if( *p == ']' ) ++p; for( ; *p != ']' && !islf_or_nul( *p ); ++p ) if( *p == '[' && ( ( d = p[1] ) == '.' || d == ':' || d == '=' ) ) for( ++p, c = *++p; *p != ']' || c != d; ++p ) if( islf_or_nul( c = *p ) ) return 0; return ( ( *p == ']' ) ? p : 0 ); } /* Copy a pattern string from the command buffer. If successful, return a pointer to the copy and point *ibufpp to the closing delimiter, final newline, or terminating zero. Do not copy the closing delimiter or final newline. */ static char * extract_pattern( const char ** const ibufpp, const char delimiter ) { static char * buf = 0; static int bufsz = 0; const char * nd = *ibufpp; int len; while( *nd != delimiter && !islf_or_nul( *nd ) ) { if( *nd == '[' ) { nd = parse_char_class( ++nd ); if( !nd ) { set_error_msg( "Unbalanced brackets ([])" ); return 0; } } else if( *nd == '\\' && islf_or_nul( *++nd ) ) { set_error_msg( "Trailing backslash (\\)" ); return 0; } ++nd; } len = nd - *ibufpp; if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return 0; memcpy( buf, *ibufpp, len ); buf[len] = 0; *ibufpp = nd; if( isbinary() ) nul_to_newline( buf, len ); return buf; } /* Return pointer to compiled regex (last_regexp), different from subst_regexp. Return 0 if error. */ static regex_t * compile_regex( const char * const pat, const bool ignore_case ) { static regex_t store[3]; /* space for three compiled regexes */ regex_t * exp; int n; for( n = 0; n < 3; ++n ) if( ( exp = &store[n] ) != last_regexp && exp != subst_regexp ) break; const int cflags = ( extended_regexp() ? REG_EXTENDED : 0 ) | ( ignore_case ? REG_ICASE : 0 ); n = regcomp( exp, pat, cflags ); if( n ) { char buf[80]; regerror( n, last_regexp, buf, sizeof buf ); set_error_msg( buf ); return 0; } /* free last_regexp if compiled and different from subst_regexp */ if( last_regexp && last_regexp != subst_regexp ) regfree( last_regexp ); last_regexp = exp; return last_regexp; } /* return pointer to compiled regex from command buffer, or to previous compiled regex if empty RE. return 0 if error */ static regex_t * get_compiled_regex( const char ** const ibufpp ) { const char delimiter = **ibufpp; if( delimiter == ' ' || islf_or_nul( delimiter ) ) { set_error_msg( inv_pat_del ); return 0; } if( *++*ibufpp == delimiter || islf_or_nul( **ibufpp ) ) /* empty RE */ { if( !last_regexp ) { set_error_msg( no_prev_pat ); return 0; } if( **ibufpp == delimiter && *++*ibufpp == 'I' ) /* remove delimiter */ { set_error_msg( inv_i_suf ); return 0; } return last_regexp; } else { const char * const pat = extract_pattern( ibufpp, delimiter ); if( !pat ) return 0; bool ignore_case = false; if( **ibufpp == delimiter && *++*ibufpp == 'I' ) /* remove delimiter */ { ignore_case = true; ++*ibufpp; } /* remove suffix */ return compile_regex( pat, ignore_case ); } } /* Extract RE pattern (may be empty) from the command buffer. Return 0 if error. */ const char * get_pattern_for_s( const char ** const ibufpp ) { const char delimiter = **ibufpp; if( delimiter == ' ' || delimiter == '\n' ) { set_error_msg( inv_pat_del ); return 0; } if( *++*ibufpp == delimiter ) /* empty RE */ { if( !last_regexp ) { set_error_msg( no_prev_pat ); return 0; } return ""; } const char * const pat = extract_pattern( ibufpp, delimiter ); if( !pat ) return 0; if( **ibufpp != delimiter ) { set_error_msg( mis_pat_del ); return 0; } return pat; } bool set_subst_regex( const char * const pat, const bool ignore_case ) { if( !pat ) return false; if( !*pat && ignore_case ) { set_error_msg( inv_i_suf ); return false; } disable_interrupts(); regex_t * exp = *pat ? compile_regex( pat, ignore_case ) : last_regexp; if( exp && exp != subst_regexp ) { if( subst_regexp ) regfree( subst_regexp ); subst_regexp = exp; } enable_interrupts(); return ( exp ? true : false ); } /* set subst_regexp to last RE found */ bool replace_subst_re_by_search_re( void ) { if( !last_regexp ) { set_error_msg( no_prev_pat ); return false; } if( last_regexp != subst_regexp ) { disable_interrupts(); if( subst_regexp ) regfree( subst_regexp ); subst_regexp = last_regexp; enable_interrupts(); } return true; } /* add lines matching a regular expression to the global-active list */ bool build_active_list( const char ** const ibufpp, const int first_addr, const int second_addr, const bool match ) { int addr; const regex_t * const exp = get_compiled_regex( ibufpp ); if( !exp ) return false; clear_active_list(); const line_node * lp = search_line_node( first_addr ); for( addr = first_addr; addr <= second_addr; ++addr, lp = lp->q_forw ) { char * const s = get_sbuf_line( lp ); if( !s ) return false; if( isbinary() ) nul_to_newline( s, lp->len ); if( match == !regexec( exp, s, 0, 0, 0 ) && !set_active_node( lp ) ) return false; } return true; } /* return the address of the next line matching a regular expression in a given direction. wrap around begin/end of editor buffer if necessary */ int next_matching_node_addr( const char ** const ibufpp ) { const bool forward = ( **ibufpp == '/' ); const regex_t * const exp = get_compiled_regex( ibufpp ); int addr = current_addr(); if( !exp ) return -1; do { addr = ( forward ? inc_addr( addr ) : dec_addr( addr ) ); if( addr ) { const line_node * const lp = search_line_node( addr ); char * const s = get_sbuf_line( lp ); if( !s ) return -1; if( isbinary() ) nul_to_newline( s, lp->len ); if( !regexec( exp, s, 0, 0, 0 ) ) return addr; } } while( addr != current_addr() ); set_error_msg( no_match ); return -1; } /* Extract substitution replacement from the command buffer. If isglobal, newlines in command-list are unescaped. */ bool extract_replacement( const char ** const ibufpp, const bool isglobal ) { static char * buf = 0; /* temporary buffer */ static int bufsz = 0; int i = 0; const char delimiter = **ibufpp; if( delimiter == '\n' ) { set_error_msg( mis_pat_del ); return false; } ++*ibufpp; if( **ibufpp == '%' && /* replacement is a single '%' */ ( (*ibufpp)[1] == delimiter || ( (*ibufpp)[1] == '\n' && ( !isglobal || (*ibufpp)[2] == 0 ) ) ) ) { ++*ibufpp; if( !rbuf ) { set_error_msg( no_prev_subst ); return false; } return true; } while( **ibufpp != delimiter ) { if( **ibufpp == '\n' && ( !isglobal || (*ibufpp)[1] == 0 ) ) break; if( !resize_buffer( &buf, &bufsz, i + 2 ) ) return false; if( ( buf[i++] = *(*ibufpp)++ ) == '\\' && ( buf[i++] = *(*ibufpp)++ ) == '\n' && !isglobal ) { /* not reached if isglobal; in command-list, newlines are unescaped */ int size = 0; *ibufpp = get_stdin_line( &size ); if( !*ibufpp ) return false; /* error */ if( size <= 0 ) return false; /* EOF */ } } /* make sure that buf gets allocated if empty replacement */ if( !resize_buffer( &buf, &bufsz, i + 1 ) ) return false; buf[i] = 0; disable_interrupts(); { char * p = buf; buf = rbuf; rbuf = p; /* swap buffers */ rlen = i; i = bufsz; bufsz = rbufsz; rbufsz = i; } enable_interrupts(); return true; } /* Produce replacement text from matched text and replacement template. Return new offset to end of replacement text, or -1 if error. */ static int replace_matched_text( char ** txtbufp, int * const txtbufszp, const char * const txt, const regmatch_t * const rm, int offset, const int re_nsub ) { int i; for( i = 0 ; i < rlen; ++i ) { int n; if( rbuf[i] == '&' ) { int j = rm[0].rm_so; int k = rm[0].rm_eo; if( !resize_buffer( txtbufp, txtbufszp, offset - j + k ) ) return -1; while( j < k ) (*txtbufp)[offset++] = txt[j++]; } else if( rbuf[i] == '\\' && rbuf[++i] >= '1' && rbuf[i] <= '9' && ( n = rbuf[i] - '0' ) <= re_nsub ) { int j = rm[n].rm_so; int k = rm[n].rm_eo; if( !resize_buffer( txtbufp, txtbufszp, offset - j + k ) ) return -1; while( j < k ) (*txtbufp)[offset++] = txt[j++]; } else /* preceding 'if' skipped escaping backslashes */ { if( !resize_buffer( txtbufp, txtbufszp, offset + 1 ) ) return -1; (*txtbufp)[offset++] = rbuf[i]; } } if( !resize_buffer( txtbufp, txtbufszp, offset + 1 ) ) return -1; (*txtbufp)[offset] = 0; return offset; } /* Produce new text with one or all matches replaced in a line. Return size of the new line text, 0 if no change, -1 if error */ static int line_replace( char ** txtbufp, int * const txtbufszp, const line_node * const lp, const int snum ) { enum { se_max = 30 }; /* max subexpressions in a regular expression */ regmatch_t rm[se_max]; char * txt = get_sbuf_line( lp ); const char * eot; int i = 0, offset = 0; const bool global = ( snum <= 0 ); bool changed = false; if( !txt ) return -1; if( isbinary() ) nul_to_newline( txt, lp->len ); eot = txt + lp->len; if( !regexec( subst_regexp, txt, se_max, rm, 0 ) ) { int matchno = 0; bool infloop = false; do { if( global || snum == ++matchno ) { changed = true; i = rm[0].rm_so; if( !resize_buffer( txtbufp, txtbufszp, offset + i ) ) return -1; if( isbinary() ) newline_to_nul( txt, rm[0].rm_eo ); memcpy( *txtbufp + offset, txt, i ); offset += i; offset = replace_matched_text( txtbufp, txtbufszp, txt, rm, offset, subst_regexp->re_nsub ); if( offset < 0 ) return -1; } else { i = rm[0].rm_eo; if( !resize_buffer( txtbufp, txtbufszp, offset + i ) ) return -1; if( isbinary() ) newline_to_nul( txt, i ); memcpy( *txtbufp + offset, txt, i ); offset += i; } txt += rm[0].rm_eo; if( global && rm[0].rm_eo == 0 ) { if( !infloop ) infloop = true; /* 's/^/#/g' is valid */ else { set_error_msg( "Infinite substitution loop" ); return -1; } } } while( *txt && ( !changed || global ) && !regexec( subst_regexp, txt, se_max, rm, REG_NOTBOL ) ); i = eot - txt; if( !resize_buffer( txtbufp, txtbufszp, offset + i + 2 ) ) return -1; if( isbinary() ) newline_to_nul( txt, i ); memcpy( *txtbufp + offset, txt, i ); /* tail copy */ memcpy( *txtbufp + offset + i, "\n", 2 ); } return ( changed ? offset + i + 1 : 0 ); } /* for each line in a range, change text matching a regular expression according to a substitution template (replacement); return false if error */ bool search_and_replace( const int first_addr, const int second_addr, const int snum, const bool isglobal ) { static char * txtbuf = 0; /* new text of line buffer */ static int txtbufsz = 0; /* new text of line buffer size */ int addr = first_addr; int lc; bool match_found = false; for( lc = 0; lc <= second_addr - first_addr; ++lc, ++addr ) { const line_node * const lp = search_line_node( addr ); const int size = line_replace( &txtbuf, &txtbufsz, lp, snum ); if( size < 0 ) return false; if( size ) { const char * txt = txtbuf; const char * const eot = txtbuf + size; undo_atom * up = 0; disable_interrupts(); if( !delete_lines( addr, addr, isglobal ) ) { enable_interrupts(); return false; } set_current_addr( addr - 1 ); do { txt = put_sbuf_line( txt, eot - txt ); if( !txt ) { enable_interrupts(); return false; } if( up ) up->tail = search_line_node( current_addr() ); else { up = push_undo_atom( UADD, current_addr(), current_addr() ); if( !up ) { enable_interrupts(); return false; } } } while( txt != eot ); enable_interrupts(); addr = current_addr(); match_found = true; } } if( !match_found && !isglobal ) { set_error_msg( no_match ); return false; } return true; } ed_1.21.1/carg_parser.c0000644000175000017500000002273214735745325014561 0ustar dogslegdogsleg/* Arg_parser - POSIX/GNU command-line argument parser. (C version) Copyright (C) 2006-2025 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include "carg_parser.h" /* assure at least a minimum size for buffer 'buf' */ static void * ap_resize_buffer( void * buf, const int min_size ) { if( buf ) buf = realloc( buf, min_size ); else buf = malloc( min_size ); return buf; } static char push_back_record( Arg_parser * const ap, const int code, const char * const long_name, const char * const argument ) { ap_Record * p; void * tmp = ap_resize_buffer( ap->data, ( ap->data_size + 1 ) * sizeof (ap_Record) ); if( !tmp ) return 0; ap->data = (ap_Record *)tmp; p = &(ap->data[ap->data_size]); p->code = code; if( long_name ) { const int len = strlen( long_name ); p->parsed_name = (char *)malloc( len + 2 + 1 ); if( !p->parsed_name ) return 0; p->parsed_name[0] = p->parsed_name[1] = '-'; strncpy( p->parsed_name + 2, long_name, len + 1 ); } else if( code > 0 && code < 256 ) { p->parsed_name = (char *)malloc( 2 + 1 ); if( !p->parsed_name ) return 0; p->parsed_name[0] = '-'; p->parsed_name[1] = code; p->parsed_name[2] = 0; } else p->parsed_name = 0; if( argument ) { const int len = strlen( argument ); p->argument = (char *)malloc( len + 1 ); if( !p->argument ) { free( p->parsed_name ); return 0; } strncpy( p->argument, argument, len + 1 ); } else p->argument = 0; ++ap->data_size; return 1; } static char add_error( Arg_parser * const ap, const char * const msg ) { const int len = strlen( msg ); void * tmp = ap_resize_buffer( ap->error, ap->error_size + len + 1 ); if( !tmp ) return 0; ap->error = (char *)tmp; strncpy( ap->error + ap->error_size, msg, len + 1 ); ap->error_size += len; return 1; } static void free_data( Arg_parser * const ap ) { int i; for( i = 0; i < ap->data_size; ++i ) { free( ap->data[i].argument ); free( ap->data[i].parsed_name ); } if( ap->data ) { free( ap->data ); ap->data = 0; } ap->data_size = 0; } /* Return 0 only if out of memory. */ static char parse_long_option( Arg_parser * const ap, const char * const opt, const char * const arg, const ap_Option options[], int * const argindp ) { unsigned len; int index = -1, i; char exact = 0, ambig = 0; for( len = 0; opt[len+2] && opt[len+2] != '='; ++len ) ; /* Test all long options for either exact match or abbreviated matches. */ for( i = 0; options[i].code != 0; ++i ) if( options[i].long_name && strncmp( options[i].long_name, &opt[2], len ) == 0 ) { if( strlen( options[i].long_name ) == len ) /* Exact match found */ { index = i; exact = 1; break; } else if( index < 0 ) index = i; /* First nonexact match found */ else if( options[index].code != options[i].code || options[index].has_arg != options[i].has_arg ) ambig = 1; /* Second or later nonexact match found */ } if( ambig && !exact ) { add_error( ap, "option '" ); add_error( ap, opt ); add_error( ap, "' is ambiguous" ); return 1; } if( index < 0 ) /* nothing found */ { add_error( ap, "unrecognized option '" ); add_error( ap, opt ); add_error( ap, "'" ); return 1; } ++*argindp; if( opt[len+2] ) /* '--=' syntax */ { if( options[index].has_arg == ap_no ) { add_error( ap, "option '--" ); add_error( ap, options[index].long_name ); add_error( ap, "' doesn't allow an argument" ); return 1; } if( options[index].has_arg == ap_yes && !opt[len+3] ) { add_error( ap, "option '--" ); add_error( ap, options[index].long_name ); add_error( ap, "' requires an argument" ); return 1; } return push_back_record( ap, options[index].code, options[index].long_name, &opt[len+3] ); /* argument may be empty */ } if( options[index].has_arg == ap_yes || options[index].has_arg == ap_yme ) { if( !arg || ( options[index].has_arg == ap_yes && !arg[0] ) ) { add_error( ap, "option '--" ); add_error( ap, options[index].long_name ); add_error( ap, "' requires an argument" ); return 1; } ++*argindp; return push_back_record( ap, options[index].code, options[index].long_name, arg ); /* argument may be empty */ } return push_back_record( ap, options[index].code, options[index].long_name, 0 ); } /* Return 0 only if out of memory. */ static char parse_short_option( Arg_parser * const ap, const char * const opt, const char * const arg, const ap_Option options[], int * const argindp ) { int cind = 1; /* character index in opt */ while( cind > 0 ) { int index = -1, i; const unsigned char c = opt[cind]; char code_str[2]; code_str[0] = c; code_str[1] = 0; if( c != 0 ) for( i = 0; options[i].code; ++i ) if( c == options[i].code ) { index = i; break; } if( index < 0 ) { add_error( ap, "invalid option -- '" ); add_error( ap, code_str ); add_error( ap, "'" ); return 1; } if( opt[++cind] == 0 ) { ++*argindp; cind = 0; } /* opt finished */ if( options[index].has_arg != ap_no && cind > 0 && opt[cind] ) { if( !push_back_record( ap, c, 0, &opt[cind] ) ) return 0; ++*argindp; cind = 0; } else if( options[index].has_arg == ap_yes || options[index].has_arg == ap_yme ) { if( !arg || ( options[index].has_arg == ap_yes && !arg[0] ) ) { add_error( ap, "option requires an argument -- '" ); add_error( ap, code_str ); add_error( ap, "'" ); return 1; } ++*argindp; cind = 0; /* argument may be empty */ if( !push_back_record( ap, c, 0, arg ) ) return 0; } else if( !push_back_record( ap, c, 0, 0 ) ) return 0; } return 1; } char ap_init( Arg_parser * const ap, const int argc, const char * const argv[], const ap_Option options[], const char in_order ) { const char ** non_options = 0; /* skipped non-options */ int non_options_size = 0; /* number of skipped non-options */ int argind = 1; /* index in argv */ char done = 0; /* false until success */ ap->data = 0; ap->error = 0; ap->data_size = 0; ap->error_size = 0; if( argc < 2 || !argv || !options ) return 1; while( argind < argc ) { const unsigned char ch1 = argv[argind][0]; const unsigned char ch2 = ch1 ? argv[argind][1] : 0; if( ch1 == '-' && ch2 ) /* we found an option */ { const char * const opt = argv[argind]; const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0; if( ch2 == '-' ) { if( !argv[argind][2] ) { ++argind; break; } /* we found "--" */ else if( !parse_long_option( ap, opt, arg, options, &argind ) ) goto out; } else if( !parse_short_option( ap, opt, arg, options, &argind ) ) goto out; if( ap->error ) break; } else { if( in_order ) { if( !push_back_record( ap, 0, 0, argv[argind++] ) ) goto out; } else { void * tmp = ap_resize_buffer( non_options, ( non_options_size + 1 ) * sizeof *non_options ); if( !tmp ) goto out; non_options = (const char **)tmp; non_options[non_options_size++] = argv[argind++]; } } } if( ap->error ) free_data( ap ); else { int i; for( i = 0; i < non_options_size; ++i ) if( !push_back_record( ap, 0, 0, non_options[i] ) ) goto out; while( argind < argc ) if( !push_back_record( ap, 0, 0, argv[argind++] ) ) goto out; } done = 1; out: if( non_options ) free( non_options ); return done; } void ap_free( Arg_parser * const ap ) { free_data( ap ); if( ap->error ) { free( ap->error ); ap->error = 0; } ap->error_size = 0; } const char * ap_error( const Arg_parser * const ap ) { return ap->error; } int ap_arguments( const Arg_parser * const ap ) { return ap->data_size; } int ap_code( const Arg_parser * const ap, const int i ) { if( i < 0 || i >= ap_arguments( ap ) ) return 0; return ap->data[i].code; } const char * ap_parsed_name( const Arg_parser * const ap, const int i ) { if( i < 0 || i >= ap_arguments( ap ) || !ap->data[i].parsed_name ) return ""; return ap->data[i].parsed_name; } const char * ap_argument( const Arg_parser * const ap, const int i ) { if( i < 0 || i >= ap_arguments( ap ) || !ap->data[i].argument ) return ""; return ap->data[i].argument; } ed_1.21.1/configure0000755000175000017500000001442114767532271014027 0ustar dogslegdogsleg#! /bin/sh # configure script for GNU ed - The GNU line editor # Copyright (C) 2006-2025 Antonio Diaz Diaz. # # This configure script is free software: you have unlimited permission # to copy, distribute, and modify it. pkgname=ed pkgversion=1.21.1 progname=ed srctrigger=doc/${pkgname}.texi # clear some things potentially inherited from environment. LC_ALL=C export LC_ALL srcdir= prefix=/usr/local exec_prefix='$(prefix)' bindir='$(exec_prefix)/bin' datarootdir='$(prefix)/share' infodir='$(datarootdir)/info' mandir='$(datarootdir)/man' program_prefix= CC=gcc CPPFLAGS= CFLAGS='-Wall -W -O2' LDFLAGS= MAKEINFO=makeinfo # checking whether we are using GNU C. /bin/sh -c "${CC} --version" > /dev/null 2>&1 || { CC=cc ; CFLAGS=-O2 ; } # Loop over all args args= no_create= while [ $# != 0 ] ; do # Get the first arg, and shuffle option=$1 ; arg2=no shift # Add the argument quoted to args if [ -z "${args}" ] ; then args="\"${option}\"" else args="${args} \"${option}\"" ; fi # Split out the argument for options that take them case ${option} in *=*) optarg=`echo "${option}" | sed -e 's,^[^=]*=,,;s,/$,,'` ;; esac # Process the options case ${option} in --help | -h) echo "Usage: $0 [OPTION]... [VAR=VALUE]..." echo echo "To assign makefile variables (e.g., CC, CFLAGS...), specify them as" echo "arguments to configure in the form VAR=VALUE." echo echo "Options and variables: [defaults in brackets]" echo " -h, --help display this help and exit" echo " -V, --version output version information and exit" echo " --srcdir=DIR find the source code in DIR [. or ..]" echo " --prefix=DIR install into DIR [${prefix}]" echo " --exec-prefix=DIR base directory for arch-dependent files [${exec_prefix}]" echo " --bindir=DIR user executables directory [${bindir}]" echo " --datarootdir=DIR base directory for doc and data [${datarootdir}]" echo " --infodir=DIR info files directory [${infodir}]" echo " --mandir=DIR man pages directory [${mandir}]" echo " --program-prefix=NAME install program and documentation prefixed with NAME" echo " CC=COMPILER C compiler to use [${CC}]" echo " CPPFLAGS=OPTIONS command-line options for the preprocessor [${CPPFLAGS}]" echo " CFLAGS=OPTIONS command-line options for the C compiler [${CFLAGS}]" echo " CFLAGS+=OPTIONS append options to the current value of CFLAGS" echo " LDFLAGS=OPTIONS command-line options for the linker [${LDFLAGS}]" echo " MAKEINFO=NAME makeinfo program to use [${MAKEINFO}]" echo exit 0 ;; --version | -V) echo "Configure script for GNU ${pkgname} version ${pkgversion}" exit 0 ;; --srcdir) srcdir=$1 ; arg2=yes ;; --prefix) prefix=$1 ; arg2=yes ;; --exec-prefix) exec_prefix=$1 ; arg2=yes ;; --bindir) bindir=$1 ; arg2=yes ;; --datarootdir) datarootdir=$1 ; arg2=yes ;; --infodir) infodir=$1 ; arg2=yes ;; --mandir) mandir=$1 ; arg2=yes ;; --program-prefix) program_prefix=$1 ; arg2=yes ;; --srcdir=*) srcdir=${optarg} ;; --prefix=*) prefix=${optarg} ;; --exec-prefix=*) exec_prefix=${optarg} ;; --bindir=*) bindir=${optarg} ;; --datarootdir=*) datarootdir=${optarg} ;; --infodir=*) infodir=${optarg} ;; --mandir=*) mandir=${optarg} ;; --program-prefix=*) program_prefix=${optarg} ;; --no-create) no_create=yes ;; CC=*) CC=${optarg} ;; CPPFLAGS=*) CPPFLAGS=${optarg} ;; CFLAGS=*) CFLAGS=${optarg} ;; CFLAGS+=*) CFLAGS="${CFLAGS} ${optarg}" ;; LDFLAGS=*) LDFLAGS=${optarg} ;; MAKEINFO=*) MAKEINFO=${optarg} ;; --*) echo "configure: WARNING: unrecognized option: '${option}'" 1>&2 ;; *=* | *-*-*) ;; *) echo "configure: unrecognized option: '${option}'" 1>&2 echo "Try 'configure --help' for more information." 1>&2 exit 1 ;; esac # Check whether the option took a separate argument if [ "${arg2}" = yes ] ; then if [ $# != 0 ] ; then args="${args} \"$1\"" ; shift else echo "configure: Missing argument to '${option}'" 1>&2 exit 1 fi fi done # Find the source code, if location was not specified. srcdirtext= if [ -z "${srcdir}" ] ; then srcdirtext="or . or .." ; srcdir=. if [ ! -r "${srcdir}/${srctrigger}" ] ; then srcdir=.. ; fi if [ ! -r "${srcdir}/${srctrigger}" ] ; then ## the sed command below emulates the dirname command srcdir=`echo "$0" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` fi fi if [ ! -r "${srcdir}/${srctrigger}" ] ; then echo "configure: Can't find source code in ${srcdir} ${srcdirtext}" 1>&2 echo "configure: (At least ${srctrigger} is missing)." 1>&2 exit 1 fi # Set srcdir to . if that's what it is. if [ "`pwd`" = "`cd "${srcdir}" ; pwd`" ] ; then srcdir=. ; fi echo if [ -z "${no_create}" ] ; then echo "creating config.status" rm -f config.status cat > config.status << EOF #! /bin/sh # This file was generated automatically by configure. Don't edit. # Run this file to recreate the current configuration. # # This script is free software: you have unlimited permission # to copy, distribute, and modify it. exec /bin/sh "$0" ${args} --no-create EOF chmod +x config.status fi echo "creating Makefile" echo "VPATH = ${srcdir}" echo "prefix = ${prefix}" echo "exec_prefix = ${exec_prefix}" echo "bindir = ${bindir}" echo "datarootdir = ${datarootdir}" echo "infodir = ${infodir}" echo "mandir = ${mandir}" echo "program_prefix = ${program_prefix}" echo "CC = ${CC}" echo "CPPFLAGS = ${CPPFLAGS}" echo "CFLAGS = ${CFLAGS}" echo "LDFLAGS = ${LDFLAGS}" echo "MAKEINFO = ${MAKEINFO}" rm -f Makefile cat > Makefile << EOF # Makefile for GNU ed - The GNU line editor # Copyright (C) 2006-2025 Antonio Diaz Diaz. # This file was generated automatically by configure. Don't edit. # # This Makefile is free software: you have unlimited permission # to copy, distribute, and modify it. pkgname = ${pkgname} pkgversion = ${pkgversion} progname = ${progname} VPATH = ${srcdir} prefix = ${prefix} exec_prefix = ${exec_prefix} bindir = ${bindir} datarootdir = ${datarootdir} infodir = ${infodir} mandir = ${mandir} program_prefix = ${program_prefix} CC = ${CC} CPPFLAGS = ${CPPFLAGS} CFLAGS = ${CFLAGS} LDFLAGS = ${LDFLAGS} MAKEINFO = ${MAKEINFO} EOF cat "${srcdir}/Makefile.in" >> Makefile echo "OK. Now you can run make." ed_1.21.1/testsuite/0000775000175000017500000000000000000000000014074 5ustar dogslegdogsleged_1.21.1/testsuite/s13.err0000644000175000017500000000002412754404174015255 0ustar dogslegdogslegH s/./&/3g w out.ro ed_1.21.1/testsuite/q.r0000644000175000017500000000152712754404174014571 0ustar dogslegdogslegThis natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. ed_1.21.1/testsuite/w3.err0000644000175000017500000000003112754404174015176 0ustar dogslegdogslegH wqp bad_write w out.ro ed_1.21.1/testsuite/filter.ed0000644000175000017500000000010414336141030015716 0ustar dogslegdogslegH 6,10w !sort > tmp.txt 2>&1 10r tmp.txt 6,10d !rm tmp.txt wq out.o ed_1.21.1/testsuite/g.r0000644000175000017500000000177514335517325014564 0ustar dogslegdogslegtheir families. heLp! world no anxiety about providing the means of subsistence for themselves and heLp! world which should live in ease, happiness, and comparative leisure; and feel heLp! world decisive against the possible existence of a society, all the members of heLp! world of it even for a single century. And it appears, therefore, to be hello world chaos agrarian regulations in their utmost extent, could remove the pressure heLp! world of this law which pervades all hello world nature. No fancied equality, no heLp! world comparison of this. I see no way by which man can escape from the weight hello world chaos All other arguments are of slight and subordinate consideration in hello world chaos me appears insurmountable in the way to the perfectibility of society. heLp! world constantly keep their effects equal, form the great hello world that to heLp! world production in the earth, and that great law of our nature which must heLp! world This natural inequality of the two powers of population and of ed_1.21.1/testsuite/q8.err0000644000175000017500000000003512754404174015201 0ustar dogslegdogslegH w out.ro a hello . w !wc q ed_1.21.1/testsuite/c.ed0000644000175000017500000000022013363626105014664 0ustar dogslegdogslegH 1c at the top . 4c in the middle . 5c . c after the middle . $c at the bottom . u u -5,10c between middle/bottom . c to be undone . u w out.o ed_1.21.1/testsuite/e1.err0000644000175000017500000000002712754404174015157 0ustar dogslegdogslegH ee test.bin w out.ro ed_1.21.1/testsuite/r3.ed0000644000175000017500000000017712754404174015004 0ustar dogslegdogslegH # appending a binary file does not add a newline r test.bin # but undo does not remove any newline already present u w out.o ed_1.21.1/testsuite/bang2.err0000644000175000017500000000001612754404174015641 0ustar dogslegdogslegH !! w out.ro ed_1.21.1/testsuite/t.r0000644000175000017500000000410412754404174014566 0ustar dogslegdogslegThis natural inequality of the two powers of population and of their families. This natural inequality of the two powers of population and of This natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. This natural inequality of the two powers of population and of This natural inequality of the two powers of population and of This natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. ed_1.21.1/testsuite/v.r0000644000175000017500000000166112754404174014575 0ustar dogslegdogslegtheir families. order hello world no anxiety about providing the means of subsistence for themselves and order decisive against the possible existence of a society, all the members of order of it even for a single century. And it appears, therefore, to be order agrarian regulations in their utmost extent, could remove the pressure order All other arguments are of slight and subordinate consideration in order constantly keep their effects equal, form the great difficulty that to order This natural inequality of the two powers of population and of order production in the earth, and that great law of our nature which must order me appears insurmountable in the way to the perfectibility of society. order comparison of this. I see no way by which man can escape from the weight order of this law which pervades all animated nature. No fancied equality, no order which should live in ease, happiness, and comparative leisure; and feel order ed_1.21.1/testsuite/i.ed0000644000175000017500000000050312754404174014701 0ustar dogslegdogslegH # empty insert at address 0 should set the current address to 0 0i . +4i # this is not a comment . # empty insert at current address should not modify the current address i . i hello world! . i hello world!! . 0i hello world!!! . i second line . $i hello world!!!! . u u i hello world!!!!! . i to be undone . u w out.o ed_1.21.1/testsuite/d.r0000644000175000017500000000032714125411444014541 0ustar dogslegdogslegme appears insurmountable in the way to the perfectibility of society. comparison of this. I see no way by which man can escape from the weight agrarian regulations in their utmost extent, could remove the pressure ed_1.21.1/testsuite/z.err0000644000175000017500000000002012754404174015114 0ustar dogslegdogslegH $z z w out.ro ed_1.21.1/testsuite/addr1.err0000644000175000017500000000001712754404174015644 0ustar dogslegdogslegH 100 w out.ro ed_1.21.1/testsuite/t1.err0000644000175000017500000000001612754404174015174 0ustar dogslegdogslegH tt w out.ro ed_1.21.1/testsuite/k.ed0000644000175000017500000000007412754404174014706 0ustar dogslegdogslegH 7l 2ka 4kb 9kc 13kd i hello world . 'a,'bd 'c,'dd w out.o ed_1.21.1/testsuite/v.ed0000644000175000017500000000021513653646055014723 0ustar dogslegdogslegH v/[w]/m0 u u v/o/a\ hello world v/hello /s/lo/p!/\ a\ order # to be undone v/z/s/./x/g u w out.o v/hello/q # not reached a hello . w out.o ed_1.21.1/testsuite/w.ed0000644000175000017500000000025514560266134014722 0ustar dogslegdogslegH 5w ./!.z 6W ./!.z 7,8W ./!.z 9,13w \x2dhyphen\x2dprefixed\x2dfile 0r !cat < ./!.z .r \x2dhyphen\x2dprefixed\x2dfile !rm -f ./!.z '\x2dhyphen\x2dprefixed\x2dfile' wq out.o ed_1.21.1/testsuite/bang1.err0000644000175000017500000000002212754404174015635 0ustar dogslegdogslegH .!date w out.ro ed_1.21.1/testsuite/f2.err0000644000175000017500000000002512754404174015157 0ustar dogslegdogslegH ftest.bin w out.ro ed_1.21.1/testsuite/d.err0000644000175000017500000000001612754404174015073 0ustar dogslegdogslegH d1 w out.ro ed_1.21.1/testsuite/q.ed0000644000175000017500000000005413653642175014716 0ustar dogslegdogslegH w out.o a hello . Q # not reached w out.o ed_1.21.1/testsuite/u.err0000644000175000017500000000001612754404174015114 0ustar dogslegdogslegH .u w out.ro ed_1.21.1/testsuite/e1.ed0000644000175000017500000000002512754404174014755 0ustar dogslegdogslegH e test.bin w out.o ed_1.21.1/testsuite/c1.err0000644000175000017500000000003412754404174015153 0ustar dogslegdogslegH c0 hello world . w out.ro ed_1.21.1/testsuite/addr3.err0000644000175000017500000000001612754404174015645 0ustar dogslegdogslegH 0p w out.ro ed_1.21.1/testsuite/q2.err0000644000175000017500000000001612754404174015172 0ustar dogslegdogslegH .Q w out.ro ed_1.21.1/testsuite/r1.ed0000644000175000017500000000026514524133067014775 0ustar dogslegdogslegH 7r test.bin u u .r # empty read at current address should not modify the current address .r empty f test.bin -7r 0r !echo hello world 1 r !! % # to be undone r test.txt u w out.o ed_1.21.1/testsuite/m_addr.ed0000644000175000017500000000023112754404174015675 0ustar dogslegdogslegH 3 ---- 2,1 2 3m;,,;; -2,.m7 . ++,+++m. -1,m. .-4m-2 -2m-1 1,2;3,4,$;5,6,7 ;.m0 u u +;m7 -m-6 +1m+4 -3m?all? /their/-m-2 # to be undone ,1m$ u ,w out.o ed_1.21.1/testsuite/t2.err0000644000175000017500000000002112754404174015171 0ustar dogslegdogslegH t0;-1 w out.ro ed_1.21.1/testsuite/i.r0000644000175000017500000000171012754404174014553 0ustar dogslegdogsleg second line hello world!!! This natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to hello world!! hello world! # this is not a comment me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and hello world!!!!! hello world!!!! their families. ed_1.21.1/testsuite/s01.err0000644000175000017500000000002112754404174015247 0ustar dogslegdogslegH s . x w out.ro ed_1.21.1/testsuite/w.r0000644000175000017500000000263414560266134014576 0ustar dogslegdogslegAll other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. This natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. ed_1.21.1/testsuite/c2.err0000644000175000017500000000003413363626761015161 0ustar dogslegdogslegH 0c hello world . w out.ro ed_1.21.1/testsuite/e3.err0000644000175000017500000000002512754404174015157 0ustar dogslegdogslegH etest.bin w out.ro ed_1.21.1/testsuite/g3.err0000644000175000017500000000002612754404174015162 0ustar dogslegdogslegH g/./s //x/ w out.ro ed_1.21.1/testsuite/g1.err0000644000175000017500000000001512754404174015156 0ustar dogslegdogslegH g w out.ro ed_1.21.1/testsuite/t.ed0000644000175000017500000000007012754404174014713 0ustar dogslegdogslegH 1t0 2,3t2 ,t$ u u t0;/./ # to be undone ,t0 u w out.o ed_1.21.1/testsuite/s07.err0000644000175000017500000000002212754404174015256 0ustar dogslegdogslegH /./ sr w out.ro ed_1.21.1/testsuite/s21.err0000644000175000017500000000014012754404174015253 0ustar dogslegdogslegH w out.ro s/a/EOF in the middle of a replacement with newlines \ should behave as a 'q' commanded_1.21.1/testsuite/s09.err0000644000175000017500000000002612754404174015264 0ustar dogslegdogslegH s/[h[:]/x/ w out.ro ed_1.21.1/testsuite/s04.err0000644000175000017500000000003112754404174015253 0ustar dogslegdogslegH s/\a\b\c/xyz/ w out.ro ed_1.21.1/testsuite/g2.err0000644000175000017500000000002012754404174015153 0ustar dogslegdogslegH g//d w out.ro ed_1.21.1/testsuite/e5.ed0000644000175000017500000000021412754404174014761 0ustar dogslegdogslegH e test.bin # modifying the last line of a binary file adds a newline $s/x/x/ # but undo restores the line to its previous state u w out.o ed_1.21.1/testsuite/k2.err0000644000175000017500000000001612754404174015164 0ustar dogslegdogslegH kA w out.ro ed_1.21.1/testsuite/r1.r0000644000175000017500000000432112754404174014646 0ustar dogslegdogsleghello world This natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ This natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. hello world test.bin ed_1.21.1/testsuite/s14.err0000644000175000017500000000002612754404174015260 0ustar dogslegdogslegH s/./&/ sg3 w out.ro ed_1.21.1/testsuite/g.ed0000644000175000017500000000113014527646567014712 0ustar dogslegdogslegH g/./m0 g//a\ hello world # lines beginning with a `#' should be ignored g/hello /# even in global commands \ s/lo/p!/\ a\ order\ .\ # and in the command list \ i\ chaos\ .\ -1s/l/L u u 17,33g/[A-I]/-1d\ +1c\ hello world\ .\ 47 ;d # don't change current address if no match g/xxx/1d ;j g/help! world/I/chaos/d\ -;/order/;d # split lines g/./s/hello world/hello\ world/ # use replacement from last substitution g/./s/animated/%/ g/./Is/difficulty/% # to be undone g/./s//x/g u w out.o a hello . g/hello/f test.bin\ E\ !:\ e test.bin\ q\ # not reached in global command\ w out.o # not reached w out.o ed_1.21.1/testsuite/a.ed0000644000175000017500000000075114335150634014672 0ustar dogslegdogslegH # empty append at address 0 should set the current address to 0 0a . +4a # this is not a comment . # empty append at current address should not modify the current address a . a hello world! . a hello world!! . 0a hello world!!! . !read one # shell escape should not modify the current address text for the read shell command above a shell escape marker . $a hello world!!!! . u u a hello world!!!!! . # match the first line 0;/^hello world!!!$/a second line . a to be undone . u w out.o ed_1.21.1/testsuite/f1.err0000644000175000017500000000002712754404174015160 0ustar dogslegdogslegH .f test.bin w out.ro ed_1.21.1/testsuite/e3.r0000644000175000017500000000040012754404174014625 0ustar dogslegdogsleg  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ed_1.21.1/testsuite/check.sh0000755000175000017500000001042714736572031015561 0ustar dogslegdogsleg#! /bin/sh # check script for GNU ed - The GNU line editor # Copyright (C) 2006-2025 Antonio Diaz Diaz. # # This script is free software; you have unlimited permission # to copy, distribute, and modify it. LC_ALL=C export LC_ALL objdir=`pwd` testdir=`cd "$1" ; pwd` ED="${objdir}"/ed framework_failure() { echo "failure in testing framework" ; exit 1 ; } if [ ! -f "${ED}" ] || [ ! -x "${ED}" ] ; then echo "${ED}: cannot execute" exit 1 fi if [ -d tmp ] ; then rm -rf tmp ; fi mkdir tmp cd "${objdir}"/tmp || framework_failure cp "${testdir}"/test.txt test.txt || framework_failure cp "${testdir}"/test.bin test.bin || framework_failure touch empty || framework_failure # used also by r1.ed fail=0 test_failed() { fail=1 ; printf " $1" ; [ -z "$2" ] || printf "($2)" ; } printf "testing ed-%s...\n" "$2" "${ED}" -q nx_file < empty [ $? = 2 ] || test_failed $LINENO "${ED}" -q +0 test.txt < empty # invalid line number [ $? = 1 ] || test_failed $LINENO "${ED}" -qs +/foobar test.txt < empty # no match [ $? = 1 ] || test_failed $LINENO "${ED}" -qs +?foobar test.txt < empty # no match [ $? = 1 ] || test_failed $LINENO "${ED}" -qs +/[a-z test.txt < empty # syntax error [ $? = 1 ] || test_failed $LINENO "${ED}" -qs -l +/foobar test.txt < empty # -l has no effect [ $? = 1 ] || test_failed $LINENO echo "q" | "${ED}" -qs +/foobar test.txt || test_failed $LINENO echo "p" | "${ED}" -s +7 test.txt | grep -q 'animated' || test_failed $LINENO echo "p" | "${ED}" -s +7 test.txt | grep -q 'the' && test_failed $LINENO echo "p" | "${ED}" -s +/which test.txt | grep -q 'must' || test_failed $LINENO echo "p" | "${ED}" -s +?which test.txt | grep -q 'ease' || test_failed $LINENO echo "p" | "${ED}" -s +/[A-Z] test.txt | grep -q 'two' || test_failed $LINENO echo "p" | "${ED}" -s +?[A-Z] test.txt | grep -q 'even' || test_failed $LINENO # test that a second 'e' succeeds printf "a\nHello world!\n.\ne test.txt\nf foo.txt\nf\nh\nH\nH\nkx\nl\nn\np\nP\nP\ny\n.z\n# comment\n=\n!:\n.\ne test.txt\n8p\n" | "${ED}" -s | grep -q 'agrarian' || test_failed $LINENO echo "q" | "${ED}" -q 'name_with_bell.txt' && test_failed $LINENO echo "q" | "${ED}" -q --unsafe-names 'name_with_bell.txt' || test_failed $LINENO if [ ${fail} != 0 ] ; then echo ; fi # Run the .err scripts first with a regular file connected to standard # input, since these don't generate output; they exit with nonzero status. for i in "${testdir}"/*.err ; do if "${ED}" -s test.txt < "$i" > /dev/null 2>&1 ; then echo "*** The script $i exited abnormally ***" fail=127 fi done # Run the .err scripts again with a regular file connected to standard # input, but with '--loose-exit-status'; they should exit with zero status. for i in "${testdir}"/*.err ; do if "${ED}" -sl test.txt < "$i" > /dev/null 2>&1 ; then true else echo "*** The script $i failed '--loose-exit-status' ***" fail=127 fi done # Run the .err scripts again as pipes - these should exit with nonzero # status without altering the contents of the buffer; the produced # 'out.ro' must be identical to 'test.txt'. for i in "${testdir}"/*.err ; do base=`echo "$i" | sed 's,^.*/,,;s,\.err$,,'` # remove dir and ext if cat "$i" | "${ED}" -s test.txt > /dev/null 2>&1 ; then echo "*** The piped script $i exited abnormally ***" fail=127 else if cmp -s out.ro test.txt ; then true else mv -f out.ro ${base}.ro echo "*** Output ${base}.ro of piped script $i is incorrect ***" fail=127 fi fi rm -f out.ro done # Run the .ed scripts and compare their output against the .r files, which # contain the correct output. # The .ed scripts should exit with zero status. for i in "${testdir}"/*.ed ; do base=`echo "$i" | sed 's,^.*/,,;s,\.ed$,,'` # remove dir and ext if "${ED}" test.txt < "$i" > out.log 2>&1 ; then if cmp -s out.o "${testdir}"/${base}.r ; then true else mv -f out.o ${base}.o echo "*** Output ${base}.o of script $i is incorrect ***" fail=127 fi else mv -f out.log ${base}.log echo "*** The script $i exited abnormally ***" fail=127 fi rm -f out.o out.log done rm -f test.txt test.bin empty if [ ${fail} = 0 ] ; then echo "tests completed successfully." cd "${objdir}" && rm -r tmp else echo "tests failed." echo "Please, send a bug report to bug-ed@gnu.org" echo "Include the (compressed) contents of '${objdir}/tmp' in the report." fi exit ${fail} ed_1.21.1/testsuite/x.err0000644000175000017500000000001512754404174015116 0ustar dogslegdogslegH x w out.ro ed_1.21.1/testsuite/q1.err0000644000175000017500000000001612754404174015171 0ustar dogslegdogslegH .q w out.ro ed_1.21.1/testsuite/e3.ed0000644000175000017500000000005012754404174014755 0ustar dogslegdogslegH e test.bin 1d E !cat test.bin w out.o ed_1.21.1/testsuite/w1.err0000644000175000017500000000004412754404174015200 0ustar dogslegdogslegH w to/some/far-away/place w out.ro ed_1.21.1/testsuite/p1.err0000644000175000017500000000001712754404174015171 0ustar dogslegdogslegH ppp w out.ro ed_1.21.1/testsuite/addr4.err0000644000175000017500000000002012754404174015641 0ustar dogslegdogslegH 2,1p w out.ro ed_1.21.1/testsuite/q6.err0000644000175000017500000000010012754404174015170 0ustar dogslegdogslegH w out.ro a # EOF in input mode should behave as a 'q' command ed_1.21.1/testsuite/s11.err0000644000175000017500000000014612754404174015260 0ustar dogslegdogslegH # Missing pattern delimiter must not alter RE nor REPLACEMENT s/i/1/g s/1/i/ s/ s/1 s//%/g w out.ro ed_1.21.1/testsuite/a.r0000644000175000017500000000173214335146721014545 0ustar dogslegdogsleghello world!!! second line shell escape marker This natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. # this is not a comment hello world! hello world!! All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. hello world!!!! hello world!!!!! ed_1.21.1/testsuite/q3.err0000644000175000017500000000002712754404174015175 0ustar dogslegdogslegH w out.ro a hello . q ed_1.21.1/testsuite/r.err0000644000175000017500000000003112754404174015106 0ustar dogslegdogslegH r a-good-book w out.ro ed_1.21.1/testsuite/m_addr.r0000644000175000017500000000152712754404174015557 0ustar dogslegdogslegtheir families. no anxiety about providing the means of subsistence for themselves and which should live in ease, happiness, and comparative leisure; and feel decisive against the possible existence of a society, all the members of of it even for a single century. And it appears, therefore, to be agrarian regulations in their utmost extent, could remove the pressure of this law which pervades all animated nature. No fancied equality, no comparison of this. I see no way by which man can escape from the weight All other arguments are of slight and subordinate consideration in me appears insurmountable in the way to the perfectibility of society. constantly keep their effects equal, form the great difficulty that to production in the earth, and that great law of our nature which must This natural inequality of the two powers of population and of ed_1.21.1/testsuite/s19.err0000644000175000017500000000002212754404174015261 0ustar dogslegdogslegH sp.p&p w out.ro ed_1.21.1/testsuite/q4.err0000644000175000017500000000007212754404174015176 0ustar dogslegdogslegH w out.ro a hello . # EOF should behave as a 'q' command ed_1.21.1/testsuite/s.ed0000644000175000017500000000126014126662673014721 0ustar dogslegdogslegH s/fam// s/il/%/ s/ies/off\&%1spr\ing/ 1s/of/01/2 +10s/and/02/g # set current address to last modified line (6) ,s/way/03 u u s,man,04,1p 10szallzf&z1 -3s!no!%&%! 9s'it'&05&'gp 12s|of|%|p1 s/\([^ ][^ ]*\)/(\1)/pg 2s /4/sp /\(No\)/sr /\(.\)/sgpr ,s&$&$& # set last regex to subst regex //p # use last regex as subst regex 5s//%/lg ,s%^%^% 5i hello/[]world . s/[/]/ / s/[[:digit:][]/ / s/[]]/ / 7s=\((03)\) =\1\ = -1s($($( 2s/a/1/l s/a/2/n s/a/3/p s/a/4/ln s/e/5/lp s/e/6/np s/e/7/lnp s/i/8 s/u/%/ s/u/% 3s/ /_/lnp3 s0e0103 4sg 6s5 9sp 10s 11sg 12sg ,s/ (nature)/$\ (nature)/ +12s/ (for)/$\ (for)/ 6s/^/#/g 6s/$/!\ \ /g 7s,^,// line 7,g 8s,^$,// line 8,g # to be undone ,s/./x/g u w out.o ed_1.21.1/testsuite/s10.err0000644000175000017500000000002612754404174015254 0ustar dogslegdogslegH s/[h[.]/x/ w out.ro ed_1.21.1/testsuite/d.ed0000644000175000017500000000012414125415720014664 0ustar dogslegdogslegH 2 d . d ,,d -5 d u u +3,+4d /And/Id //d ??d ?All?Id # to be undone 1,$d u w out.o ed_1.21.1/testsuite/r2.r0000644000175000017500000000212712754404174014651 0ustar dogslegdogslegThis natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families.   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ed_1.21.1/testsuite/e2.r0000644000175000017500000000040012754404174014624 0ustar dogslegdogsleg  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ed_1.21.1/testsuite/e2.err0000644000175000017500000000002712754404174015160 0ustar dogslegdogslegH .e test.bin w out.ro ed_1.21.1/testsuite/r2.ed0000644000175000017500000000010612754404174014773 0ustar dogslegdogslegH # appending a binary file does not add a newline r test.bin w out.o ed_1.21.1/testsuite/i.err0000644000175000017500000000003412754404174015100 0ustar dogslegdogslegH ii hello world . w out.ro ed_1.21.1/testsuite/e4.err0000644000175000017500000000002714530360665015162 0ustar dogslegdogslegH e test.bin/ w out.ro ed_1.21.1/testsuite/e4.ed0000644000175000017500000000012712754404174014763 0ustar dogslegdogslegH e test.bin # modifying the last line of a binary file adds a newline $s/x/x/ w out.o ed_1.21.1/testsuite/w2.err0000644000175000017500000000002612754404174015201 0ustar dogslegdogslegH wbad_write w out.ro ed_1.21.1/testsuite/e1.r0000644000175000017500000000040012754404174014623 0ustar dogslegdogsleg  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ed_1.21.1/testsuite/test.bin0000644000175000017500000000040012754404174015604 0ustar dogslegdogsleg  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ed_1.21.1/testsuite/e4.r0000644000175000017500000000040112754404174014627 0ustar dogslegdogsleg  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ed_1.21.1/testsuite/s22.err0000644000175000017500000000002614127037344015254 0ustar dogslegdogslegH /./ s//x/I w out.ro ed_1.21.1/testsuite/p2.err0000644000175000017500000000001612754404174015171 0ustar dogslegdogslegH .P w out.ro ed_1.21.1/testsuite/s18.err0000644000175000017500000000002312754404174015261 0ustar dogslegdogslegH sr.r&rp w out.ro ed_1.21.1/testsuite/x.ed0000644000175000017500000000007712754404174014726 0ustar dogslegdogslegH 2,4y $x 3x u u ,y 8y $x 16 y x # to be undone ,y x u w out.o ed_1.21.1/testsuite/s03.err0000644000175000017500000000002512754404174015255 0ustar dogslegdogslegH s/[xyx/a/ w out.ro ed_1.21.1/testsuite/k.r0000644000175000017500000000054612754404174014563 0ustar dogslegdogslegThis natural inequality of the two powers of population and of All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight hello world of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure ed_1.21.1/testsuite/s16.err0000644000175000017500000000002312754404174015257 0ustar dogslegdogslegH s3.3&3p w out.ro ed_1.21.1/testsuite/c.r0000644000175000017500000000055112754404174014547 0ustar dogslegdogslegat the top production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to in the middle after the middle of this law which pervades all animated nature. No fancied equality, no between middle/bottom no anxiety about providing the means of subsistence for themselves and at the bottom ed_1.21.1/testsuite/e2.ed0000644000175000017500000000002712754404174014760 0ustar dogslegdogslegH f test.bin e w out.o ed_1.21.1/testsuite/q7.err0000644000175000017500000000013512754404174015201 0ustar dogslegdogslegH w out.ro a hello # EOF in the middle of a line in input mode should behave as a 'q' commanded_1.21.1/testsuite/filter.r0000644000175000017500000000152714336141030015601 0ustar dogslegdogslegThis natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in agrarian regulations in their utmost extent, could remove the pressure comparison of this. I see no way by which man can escape from the weight decisive against the possible existence of a society, all the members of of it even for a single century. And it appears, therefore, to be of this law which pervades all animated nature. No fancied equality, no which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. ed_1.21.1/testsuite/s12.err0000644000175000017500000000002412754404174015254 0ustar dogslegdogslegH s/./&/g3 w out.ro ed_1.21.1/testsuite/r3.r0000644000175000017500000000152712754404174014655 0ustar dogslegdogslegThis natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. ed_1.21.1/testsuite/x.r0000644000175000017500000000252012754404174014572 0ustar dogslegdogslegThis natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. their families. production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in ed_1.21.1/testsuite/h.err0000644000175000017500000000001612754404174015077 0ustar dogslegdogslegH .h w out.ro ed_1.21.1/testsuite/j.ed0000644000175000017500000000007112754404174014702 0ustar dogslegdogslegH 2,3j j 7 u u 3j j 3,3j j # to be undone 1,$j u w out.o ed_1.21.1/testsuite/addr2.err0000644000175000017500000000002012754404174015637 0ustar dogslegdogslegH -100 w out.ro ed_1.21.1/testsuite/s20.err0000644000175000017500000000003712754404174015257 0ustar dogslegdogslegH $m7 ,s/xxx/yyy/ .m$ w out.ro ed_1.21.1/testsuite/k3.err0000644000175000017500000000001712754404174015166 0ustar dogslegdogslegH 0ka w out.ro ed_1.21.1/testsuite/g4.err0000644000175000017500000000002514127300040015141 0ustar dogslegdogslegH /./ g//Id w out.ro ed_1.21.1/testsuite/e5.r0000644000175000017500000000040012754404174014627 0ustar dogslegdogsleg  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ed_1.21.1/testsuite/s08.err0000644000175000017500000000002612754404174015263 0ustar dogslegdogslegH s/[h[=]/x/ w out.ro ed_1.21.1/testsuite/s06.err0000644000175000017500000000001512754404174015257 0ustar dogslegdogslegH s w out.ro ed_1.21.1/testsuite/k1.err0000644000175000017500000000004112754404174015161 0ustar dogslegdogslegH a hello . .ka 'ad 'ap w out.ro ed_1.21.1/testsuite/j.r0000644000175000017500000000152312754404174014556 0ustar dogslegdogslegThis natural inequality of the two powers of population and of production in the earth, and that great law of our nature which mustconstantly keep their effects equal, form the great difficulty that tome appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to bedecisive against the possible existence of a society, all the members ofwhich should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. ed_1.21.1/testsuite/a.err0000644000175000017500000000003412754404174015070 0ustar dogslegdogslegH ag hello world . w out.ro ed_1.21.1/testsuite/s02.err0000644000175000017500000000002412754404174015253 0ustar dogslegdogslegH s/x*/a/g w out.ro ed_1.21.1/testsuite/q5.err0000644000175000017500000000013512754404174015177 0ustar dogslegdogslegH w out.ro a hello . # EOF in the middle of a command should behave as a 'q' command w out.roed_1.21.1/testsuite/s05.err0000644000175000017500000000002312754404174015255 0ustar dogslegdogslegH s//xyz/ w out.ro ed_1.21.1/testsuite/test.txt0000644000175000017500000000152712754404174015666 0ustar dogslegdogslegThis natural inequality of the two powers of population and of production in the earth, and that great law of our nature which must constantly keep their effects equal, form the great difficulty that to me appears insurmountable in the way to the perfectibility of society. All other arguments are of slight and subordinate consideration in comparison of this. I see no way by which man can escape from the weight of this law which pervades all animated nature. No fancied equality, no agrarian regulations in their utmost extent, could remove the pressure of it even for a single century. And it appears, therefore, to be decisive against the possible existence of a society, all the members of which should live in ease, happiness, and comparative leisure; and feel no anxiety about providing the means of subsistence for themselves and their families. ed_1.21.1/testsuite/m.err0000644000175000017500000000002112754404174015100 0ustar dogslegdogslegH 1,$m5 w out.ro ed_1.21.1/testsuite/s15.err0000644000175000017500000000002612754404174015261 0ustar dogslegdogslegH s/./&/ s3g w out.ro ed_1.21.1/testsuite/s.r0000644000175000017500000000177113436770507014600 0ustar dogslegdogsleg^This natural inequality of the two powers 01 population and of$ ^(prod8ct8on) (in) (th5) (61rth,) (2nd) (th3t) (gr74t) (law) (of) (o8r)$ (nature) (which) (must)$ ^constantly keep th1ir_effects equal, form the great difficulty that to$ ^m1 app1ars insurmountabl1 in th1 03 to th1 p1rf1ctibility of soci1ty.$ #hello world! // line 7 // line 8 ^All other arguments are of slight and subordinate consid1ration in$$ ^(comparison) (of) (this.) (I) (see) (no) (03)$ (by) (which) (04) (can) (escape) (from) (the) (weight)$ ^of this law which pervades all animated nature. (No) fanci1d equality, %no%$ ^(a)grarian regulations in their utmost extent, could r1move the pressure$ ^of it05it 1v1n for a singl1 c1ntury. And it05it app1ars, th1r1for1, to b1$ ^d1cisive against the possible existence of a society, fall the members of$ ^which should live in ease, happiness, 02 comparative leisure; 02 feel$ ^(no) (anxiety) (about) (providing) (the) (means) (of05of) (subsistence)$ (for) (themselves) (and)$ ^their off&%1spring.$ ed_1.21.1/testsuite/s17.err0000644000175000017500000000002312754404174015260 0ustar dogslegdogslegH sg.g&gp w out.ro