+2017-02-22 Antonio Diaz Diaz <antonio@gnu.org>
+
+ * Version 1.14.2 released.
+ * main.c (show_strerror) Revert to using '!scripted' instead of
+ 'verbose' to suppress diagnostics.
+
+2017-01-10 Antonio Diaz Diaz <antonio@gnu.org>
+
+ * Version 1.14.1 released.
+ * Print counts, messages, '?' and '!' to stdout instead of stderr.
+ * buffer.c (append_lines): Fixed current address after empty 'i'.
+ * regex.c (set_subst_regex): Treat missing delimiters consistently.
+ (extract_replacement): Don't replace 'a' with '%' in 's/a/%'.
+ Fixed 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): Fixed address offsets;
+ '3 ---- 2' was calculated as -2 instead of 1.
+ Accept ranges with the first address omitted.
+ (exec_command): Fixed current address after empty replacement
+ text in 'c' command.
+ Don't clear the modified status after writing the buffer to a
+ shell command. (Reported by Jérôme 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 a 'q' command.
+ * ed.texi: Fixed the description of commands 'acegijkmqrsuw'.
+ Documented that ed allows any combination of print suffixes.
+ * testsuite: Improved most tests. Simplified 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 <antonio@gnu.org>
* Version 1.13 released.
Copyright (C) 1993 François Pinard
Copyright (C) 1994 Andrew Moore
-Copyright (C) 2006-2016 Antonio Diaz Diaz.
+Copyright (C) 2006-2017 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
Requirements
------------
You will need a C compiler and a C library compatible with GNU libc.
-I use gcc 4.9.1 and 4.1.2, but the code should compile with any
+I use gcc 5.3.0 and 4.1.2, but the code should compile with any
standards compliant compiler.
Gcc is available at http://gcc.gnu.org.
explained above.
-Copyright (C) 2006-2016 Antonio Diaz Diaz.
+Copyright (C) 2006-2017 Antonio Diaz Diaz.
This file is free documentation: you have unlimited permission to copy,
distribute and modify it.
INSTALL_DATA = $(INSTALL) -m 644
INSTALL_DIR = $(INSTALL) -d -m 755
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
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"
- -install-info --info-dir="$(DESTDIR)$(infodir)" "$(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"
-rm -f "$(DESTDIR)$(bindir)/$(program_prefix)r$(progname)"
uninstall-info :
- -install-info --info-dir="$(DESTDIR)$(infodir)" --remove "$(DESTDIR)$(infodir)/$(program_prefix)$(pkgname).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 :
$(DISTNAME)/doc/$(pkgname).texi \
$(DISTNAME)/doc/fdl.texi \
$(DISTNAME)/r$(progname).in \
+ $(DISTNAME)/*.h \
+ $(DISTNAME)/*.c \
$(DISTNAME)/testsuite/check.sh \
- $(DISTNAME)/testsuite/*.t \
- $(DISTNAME)/testsuite/*.d \
+ $(DISTNAME)/testsuite/test.bin \
+ $(DISTNAME)/testsuite/test.txt \
+ $(DISTNAME)/testsuite/*.ed \
$(DISTNAME)/testsuite/*.r \
- $(DISTNAME)/testsuite/*.pr \
- $(DISTNAME)/testsuite/*.err \
- $(DISTNAME)/*.h \
- $(DISTNAME)/*.c
+ $(DISTNAME)/testsuite/*.err
rm -f $(DISTNAME)
lzip -v -9 $(DISTNAME).tar
-Changes in version 1.13:
+Changes in version 1.14:
-A memory leak and a resource leak (file not closed on error) have been
-fixed. (Both issues were reported by Cédric Picard).
+Version 1.14 is the largest bug hunt ever attempted in GNU ed. Other
+goals of version 1.14 are to complete the documentation and to remove
+any gratuitous incompatibilities with the POSIX standard. Thanks to Ori
+Avtalion for initiating all this with a couple bug reports. ;-)
+
+Byte counts, informative messages, command error messages, and the '?'
+and '!' prompts are now written to stdout instead of to stderr. The
+standard error (stderr) is now used only for diagnostic messages.
+
+The current address is now correctly set to the addressed line after an
+empty insert command.
+
+Fixed inconsistent behavior of the substitute command. It incorrectly
+reported 'Invalid pattern delimiter' when the two last delimiters were
+omitted after a null regular expression. Now it consistently reports
+'Missing pattern delimiter' if the two last delimiters are omitted after
+any regular expression (null or not).
+
+'s/a/%' has been fixed. It incorrectly replaced 'a' with '%' instead of
+using the replacement from the last substitution.
+
+An infinite loop, happening when EOF was found in the middle of a
+replacement string, has been fixed.
+
+Ed no longer accepts newlines in the replacement of a 's' command if it
+is part of the command list of a global command, because in this case
+the meaning of the newline becomes ambiguous. For the same reason, the
+last delimiter can't be omitted if the 's' command is not the last
+command in the command list.
+
+The substitute command now correctly sets the current address to the
+address of the last line on which a substitution occurred, and leaves it
+unchanged if no substitution is performed.
+
+A bug in the calculation of address offsets has been fixed. '3 ---- 2'
+was calculated as address -2 instead of the correct address 1.
+
+Address ranges with the first address omitted are now accepted.
+
+The current address is now correctly set to the addressed line (or to
+the new last line if at EOF) after an empty replacement text in the
+change command.
+
+Repeated print suffixes are now rejected. It has been documented that ed
+allows any combination of non-repeated print suffixes and combines their
+effects.
+
+The substitute command now accepts suffixes in any order.
+
+The 'repeat substitution' command now rejects multiple count suffixes.
+
+The 'p' suffix of the 'repeat substitution' command now toggles all the
+print suffixes of the last substitution.
+
+End of file on standard input now behaves as a 'q' command.
+
+The modified status is no longer cleared after writing the buffer to the
+standard input of a shell command. (Reported by Jérôme Frgacic).
+
+The descriptions of the 'a', 'c', 'e', 'g', 'i', 'j', 'k', 'm', 'q',
+'r', 's', 'u' and 'w' commands in the manual have been fixed.
+
+Most tests in the testsuite have been improved. Bug reporting has been
+simplified; only the failed logs and results are kept in the test
+directory, which can then be (tarred, compressed, and) attached to the
+bug report.
* BSD commands have been implemented wherever they do not conflict with
the POSIX standard. The BSD-ism's included are:
- * 's' (i.e., s[n][rgp]*) to repeat a previous substitution,
+ * '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.
backslash (\).
* The file commands 'E', 'e', 'r', 'W' and 'w' process a <file>
- argument for backslash escapes; i.e., any character preceded by a
- backslash is interpreted literally. If the first unescaped character
- of a <file> argument is a bang (!), then the rest of the line is
- interpreted as a shell command, and no escape processing is performed
- by GNU ed.
+ argument for backslash escapes; i.e., any character preceded by a
+ backslash is interpreted literally. If the first character of a
+ <file> argument is a bang (!), then the rest of the line is
+ interpreted as a shell command, and no escape processing is
+ performed by GNU ed.
* 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
DEVIATIONS
----------
- * For backwards compatibility, the POSIX rule that says a range of
- addresses cannot be used where only a single address is expected has
- been relaxed.
-
- * To support the BSD 's' command (see EXTENSIONS above),
- substitution patterns cannot be delimited by numbers or the characters
- 'r', 'g' and 'p'. In contrast, POSIX specifies any character expect
- space or newline can used as a delimiter.
+ * 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:
ed implementation: any moved lines are removed from the global command's
'active' list.
- * If GNU ed is invoked with a name argument prefixed by a bang (!), then
- the remainder of the argument is interpreted as a shell command. To invoke
- ed on a file whose name starts with bang, prefix the name with a
- (quoted) backslash.
-
* 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 suffixes '.t', '.d', '.r',
-'.pr' 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 verify POSIX compliance nor do they verify correct
-8-bit or long line support.
-
-The test file suffixes have the following meanings:
-.t Template - a list of ed commands from which an ed script is
- constructed
-.d Data - read by an ed script
+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 verify POSIX compliance nor do they verify 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.
-.pr Result from a piped ed script.
-.err Error - invalid ed commands that should generate an error
+.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 non-zero status without altering the contents of the
+buffer.
-The output of the tests is written to files with .o and .ro suffixes and
-compared with their corresponding .r and .pr result files.
If any test fails, the error messages look like:
*** The script u.ed exited abnormally ***
Copyright (C) 1993, 1994 Andrew Moore
-Copyright (C) 2006-2016 Antonio Diaz Diaz.
+Copyright (C) 2006-2017 Antonio Diaz Diaz.
This file is free documentation: you have unlimited permission to copy,
distribute and modify it.
Some missing tests:
-0) g/./s^@^@ - okay: NULs in commands
-1) g/./s/^@/ - okay: NULs in patterns
-2) a
- hello^V^Jworld
- . - okay: embedded newlines in insert mode
+1) g/./s^@^@ - okay: NULs in commands
+2) g/./s/^@/ - okay: NULs in patterns
3) ed -x - verify: 8-bit clean
4) ed - verify: long-line support
5) ed - verify: interactive/help mode
/* buffer.c: scratch-file buffer routines for the ed line editor. */
/* GNU ed - The GNU line editor.
Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 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
static int last_addr_ = 0; /* last address in editor buffer */
static bool isbinary_ = false; /* if set, buffer contains ASCII NULs */
static bool modified_ = false; /* if set, buffer modified since last write */
-static bool newline_added_ = false; /* if set, newline appended to input file */
static bool seek_write = false; /* seek before writing */
static FILE * sfp = 0; /* scratch file pointer */
bool modified( void ) { return modified_; }
void set_modified( const bool m ) { modified_ = m; }
-bool newline_added( void ) { return newline_added_; }
-void set_newline_added( void ) { newline_added_ = true; }
-
int inc_addr( int addr )
{ if( ++addr > last_addr_ ) addr = 0; return addr; }
/* add a line node in the editor buffer after the given line */
-static void add_line_node( line_t * const lp, const int addr )
+static void add_line_node( line_t * const lp )
{
- line_t * const prev = search_line_node( addr );
+ line_t * const prev = search_line_node( current_addr_ );
insert_node( lp, prev );
+ ++current_addr_;
++last_addr_;
}
line n; stop when either a single period is read or EOF.
Returns false if insertion fails. */
bool append_lines( const char ** const ibufpp, const int addr,
- const bool isglobal )
+ bool insert, const bool isglobal )
{
int size = 0;
undo_t * up = 0;
{
if( !isglobal )
{
- *ibufpp = get_tty_line( &size );
- if( !*ibufpp ) return false;
- if( size == 0 || (*ibufpp)[size-1] != '\n' )
- { clearerr( stdin ); return ( size == 0 ); }
+ *ibufpp = get_stdin_line( &size );
+ if( !*ibufpp ) return false; /* error */
+ if( size <= 0 ) return true; /* EOF */
}
else
{
}
if( size == 2 && **ibufpp == '.' ) { *ibufpp += size; return true; }
disable_interrupts();
- if( !put_sbuf_line( *ibufpp, size, current_addr_ ) )
+ 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
disable_interrupts();
lp = dup_line_node( np );
if( !lp ) { enable_interrupts(); return false; }
- add_line_node( lp, current_addr_++ );
+ add_line_node( lp );
if( up ) up->tail = lp;
else
{
if( isglobal ) unset_active_nodes( p->q_forw, n );
link_nodes( p, n );
last_addr_ -= to - from + 1;
- current_addr_ = from - 1;
+ current_addr_ = min( from, last_addr_ );
modified_ = true;
enable_interrupts();
return true;
if( !delete_lines( from, to, isglobal ) ) return false;
current_addr_ = from - 1;
disable_interrupts();
- if( !put_sbuf_line( buf, size, current_addr_ ) ||
+ if( !put_sbuf_line( buf, size ) ||
!push_undo_atom( UADD, current_addr_, current_addr_ ) )
{ enable_interrupts(); return false; }
modified_ = true;
/* open scratch file */
bool open_sbuf( void )
{
- isbinary_ = newline_added_ = false;
+ isbinary_ = false; reset_unterminated_line();
sfp = tmpfile();
if( !sfp )
{
disable_interrupts();
p = dup_line_node( lp );
if( !p ) { enable_interrupts(); return false; }
- add_line_node( p, current_addr_++ );
+ add_line_node( p );
if( up ) up->tail = p;
else
{
/* write a line of text to the scratch file and add a line node to the
- editor buffer; return a pointer to the end of the text */
-const char * put_sbuf_line( const char * const buf, const int size,
- const int addr )
+ editor buffer; return a pointer to the end of the text, 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 );
line_t * lp;
lp = dup_line_node( 0 );
if( !lp ) return 0;
lp->pos = sfpos; lp->len = len;
- add_line_node( lp, addr );
- ++current_addr_;
+ add_line_node( lp );
sfpos += len; /* update file position */
return p + 1;
}
{
line_t * const lp = bp->q_forw;
unmark_line_node( bp );
+ unmark_unterminated_line( bp );
free( bp );
bp = lp;
}
/* Arg_parser - POSIX/GNU command line argument parser. (C version)
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided
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 */
+ ambig = 1; /* Second or later nonexact match found */
}
if( ambig && !exact )
}
else
{
- if( !in_order )
+ if( in_order )
+ { if( !push_back_record( ap, 0, argv[argind++] ) ) return 0; }
+ else
{
void * tmp = ap_resize_buffer( non_options,
( non_options_size + 1 ) * sizeof *non_options );
non_options = (const char **)tmp;
non_options[non_options_size++] = argv[argind++];
}
- else if( !push_back_record( ap, 0, argv[argind++] ) ) return 0;
}
}
if( ap->error ) free_data( ap );
/* Arg_parser - POSIX/GNU command line argument parser. (C version)
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided
#! /bin/sh
# configure script for GNU ed - The GNU line editor
-# Copyright (C) 2006-2016 Antonio Diaz Diaz.
+# Copyright (C) 2006-2017 Antonio Diaz Diaz.
#
# This configure script is free software: you have unlimited permission
# to copy, distribute and modify it.
pkgname=ed
-pkgversion=1.13
+pkgversion=1.14.2
progname=ed
srctrigger=doc/${pkgname}.texi
LDFLAGS=
# checking whether we are using GNU C.
-${CC} --version > /dev/null 2>&1
-if [ $? != 0 ] ; then
+/bin/sh -c "${CC} --version" > /dev/null 2>&1 ||
+ {
CC=cc
- CFLAGS='-W -O2'
-fi
+ CFLAGS=-O2
+ }
# Loop over all args
args=
# Process the options
case ${option} in
--help | -h)
- echo "Usage: configure [options]"
+ 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: [defaults in brackets]"
echo " -h, --help display this help and exit"
rm -f Makefile
cat > Makefile << EOF
# Makefile for GNU ed - The GNU line editor
-# Copyright (C) 2006-2016 Antonio Diaz Diaz.
+# Copyright (C) 2006-2017 Antonio Diaz Diaz.
# This file was generated automatically by configure. Don't edit.
#
# This Makefile is free software: you have unlimited permission
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
-.TH ED "1" "January 2016" "ed 1.13" "User Commands"
+.TH ED "1" "February 2017" "ed 1.14.2" "User Commands"
.SH NAME
ed \- line-oriented text editor
.SH SYNOPSIS
run in restricted mode
.TP
\fB\-s\fR, \fB\-\-quiet\fR, \fB\-\-silent\fR
-suppress diagnostics
+suppress diagnostics, byte counts and '!' prompt
.TP
\fB\-v\fR, \fB\-\-verbose\fR
-be verbose
+be verbose; equivalent to the 'H' command
.PP
Start edit by reading in 'file' if given.
If 'file' begins with a '!', read output of shell command.
.SH COPYRIGHT
Copyright \(co 1994 Andrew L. Moore.
.br
-Copyright \(co 2016 Antonio Diaz Diaz.
+Copyright \(co 2017 Antonio Diaz Diaz.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
.br
This is free software: you are free to change and redistribute it.
* Ed: (ed). The GNU line editor
END-INFO-DIR-ENTRY
- Copyright (C) 1993, 1994, 2006-2016 Free Software Foundation, Inc.
+ Copyright (C) 1993, 1994, 2006-2017 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
The GNU ed line editor
**********************
-This manual is for GNU ed (version 1.13, 24 January 2016).
+This manual is for GNU ed (version 1.14.2, 22 February 2017).
GNU ed is a line-oriented text editor. It is used to create, display,
* GNU Free Documentation License:: How you can copy and share this manual
- Copyright (C) 1993, 1994, 2006-2016 Free Software Foundation, Inc.
+ Copyright (C) 1993, 1994, 2006-2017 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
# Move the title to its proper place.
5m0p
Sonnet #50
- # The title is now the first line, and the current line has been
- # set to this line as well.
+ # 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.
195
$
- When 'ed' opens a file, the current line is initially set to the
-last line of that file. Similarly, the move command 'm' sets the
-current line to the last line moved.
+ 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:
'-s'
'--quiet'
'--silent'
- Suppresses diagnostics. This should be used if 'ed''s standard
- input is from a script.
+ Suppresses diagnostics, the printing of byte counts by 'e', 'E',
+ 'r' and 'w' commands, and the '!' prompt after a '!' command. This
+ option may be useful if 'ed''s standard input is from a script.
'-v'
'--verbose'
- Verbose mode. This may be toggled on and off with the 'H' command.
+ Verbose mode; prints error explanations. This may be toggled on
+ and off with the 'H' command.
+ Exit status: 0 if no errors occurred; otherwise >0.
+
\1f
File: ed.info, Node: Line addressing, Next: Regular expressions, Prev: Invoking ed, Up: Top
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 last line of the file. In
-general, the current address is set to the last line affected by a
-command.
+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 "before the first line", 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. If only one address is given in a
-range, then the second address is set to the given address. If an
-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.
-
- 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.
+or a semicolon (';'). 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. The value of the first address in a range cannot exceed the
+value of the second.
+
+ 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 through last lines in the buffer. This is equivalent
- to the address range '.,$'.
+ to the address range '.;$'.
'/RE/'
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. '//' repeats the last search.
+ current line, if necessary. A null RE '//' repeats the last search.
'?RE?'
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. '??' repeats the last search.
+ line, if necessary. A null RE '??' repeats the last search.
''x'
The apostrophe-x character pair addresses the line previously
Addresses can be followed by one or more address offsets, optionally
separated by whitespace. Offsets are constructed as follows:
- * A number adds the indicated number of lines to the address.
-
* '+' 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 RE fails
+to find a matching line.
\1f
File: ed.info, Node: Regular expressions, Next: Commands, Prev: Line addressing, Up: Top
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.
+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.
An interrupt (typically <Control-C>) has the effect of aborting the
current command and returning the editor to command mode.
specified (in parenthesis).
'(.)a'
- Appends text to the buffer after the addressed line, which may be
- the address '0' (zero). Text is entered in input mode. The current
- address is set to last line entered.
+ 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 appended in their place. Text is entered
- in input mode. The current address is set to last line entered.
+ 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.
'(.,.)d'
- Deletes the addressed lines from the buffer. If there is a line
- after the deleted range, then the current address is set to this
- line. Otherwise the current address is set to the line before the
- deleted range.
+ 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.
'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. The current
- address is set to the last line read.
+ address is set to the address of the last line in the buffer.
-'e !COMMAND'
- Edits the standard output of '!COMMAND', (see the '!' command
- below). The default filename is unchanged. Any lines in the buffer
- are deleted before the output of COMMAND is read. The current
- address is set to the last line read.
+ 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.
+
+ A warning is printed if any changes have been made in the buffer
+ since the last 'w' command that wrote the entire buffer to a file.
'E FILE'
Edits FILE unconditionally. This is similar to the 'e' command,
- except that unwritten changes are discarded without warning. The
- current address is set to the last line read.
+ except that unwritten changes are discarded without warning.
'f FILE'
Sets the default filename to FILE. If FILE is not specified, then
the default unescaped filename is printed.
'(1,$)g/RE/COMMAND-LIST'
- Global command. Applies COMMAND-LIST to each of the addressed
- lines matching a regular expression RE. The current address is set
- to the line currently matched before COMMAND-LIST is executed. At
- the end of the 'g' command, the current address is set to the last
- line affected by COMMAND-LIST.
-
- At least the first command of COMMAND-LIST must appear on the same
- line as the 'g' command. 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'. 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.
+ 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. 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 first command of COMMAND-LIST must appear on the same line as
+ the 'g' command. 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/'
Interactive global command. Interactively edits the addressed lines
matching a regular expression RE. For each matching line, the line
is printed, the current address is set, and the user is prompted to
- enter a COMMAND-LIST. At the end of the 'G' command, the current
- address is set to the last line affected by (the last)
- COMMAND-LIST.
+ 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 a null command list. A single '&' repeats
begin with this command to aid in debugging.
'(.)i'
- Inserts text in the buffer before the current line. The address '0'
- (zero) is valid for this command; it is equivalent to address '1'.
- Text is entered in input mode. The current address is set to the
- last line entered.
+ Inserts text in the buffer before the addressed line. The address
+ '0' (zero) is valid for this command; it is equivalent to address
+ '1'. 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. The addressed lines are deleted from the
- buffer and replaced by a single line containing their joined text.
- The current address is set to the resultant line.
+ 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 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.
+ deleted or otherwise modified. The current address is unchanged.
'(.,.)l'
- 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. The current address is set to
- the last line printed.
+ 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, which may be the address '0'
- (zero). The current address is set to the new address of the last
- line moved.
+ 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 moved lines. The current address
+ is set to the new address of the last line moved.
'(.,.)n'
- Prints the addressed lines, preceding each line by its line number
- and a <tab>. The current address is set to the last line printed.
+ Number command. Prints the addressed lines, preceding each line by
+ its line number and a <tab>. 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 last
- line printed.
+ 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 is
default turned off.
'q'
- Quits 'ed'.
+ Quits 'ed'. A warning is printed if any changes have been made in
+ the buffer since the last 'w' command that wrote the entire buffer
+ to a file.
'Q'
Quits 'ed' unconditionally. This is similar to the 'q' command,
except that unwritten changes are discarded without warning.
'($)r FILE'
- Reads FILE to 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 current address
- is set to the last line read.
-
-'($)r !COMMAND'
- Reads to after the addressed line the standard output of
- '!command', (see the '!' command below). The default filename is
- unchanged. The current address is set to the last line read.
+ 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.
'(.,.)s/RE/REPLACEMENT/'
-'(.,.)s/RE/REPLACEMENT/g'
-'(.,.)s/RE/REPLACEMENT/N'
- Replaces text in the addressed lines matching a regular expression
- RE with REPLACEMENT. By default, only the first match in each line
- is replaced. If the 'g' (global) suffix is given, then every match
- is replaced. The N suffix, where N is a postive number, causes
- only the Nth match to be replaced. It is an error if no
- substitutions are performed on any of the addressed lines. The
- current address is set to the last line affected.
+ Substitute command. 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 's' command accepts any
+ combination of the suffixes 'g', 'COUNT', 'l', 'n', and 'p'. If
+ the 'g' (global) suffix is given, then every match is replaced.
+ The 'COUNT' suffix, where COUNT is a positive number, causes only
+ the COUNTth match to be replaced. 'g' and 'COUNT' can't be
+ specified in the same command. 'l', 'n', and 'p' are the usual
+ print suffixes. 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.
RE and REPLACEMENT may be delimited by any character other than
<space>, <newline> and the characters used by the form of the 's'
- command shown below. If one or two of the last delimiters is
- omitted, then the last line affected is printed as if the print
- suffix 'p' were specified.
+ 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
+ becomes 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 REPLACEMENT consists of a single '%', then
- REPLACEMENT from the last substitution is used. Newlines may be
- embedded in REPLACEMENT if they are escaped with a backslash ('\').
+ 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, except if the 's' command is part of a 'g'
+ or 'v' COMMAND-LIST, because in this case the meaning of the
+ escaped newline becomes ambiguous. Each backslash in REPLACEMENT
+ removes the special meaning (if any) of the following character.
'(.,.)s'
Repeats the last substitution. This form of the 's' command accepts
- a count suffix N, and any combination of the characters 'r', 'g',
- and 'p'. If a count suffix N is given, then only the Nth match is
- replaced. The 'r' suffix causes the regular expression of the last
- search to be used instead of the that of the last substitution.
- The 'g' suffix toggles the global suffix of the last substitution.
- The 'p' suffix toggles the print suffix of the last substitution.
- The current address is set to the last line affected.
+ the 'g' and 'COUNT' suffixes described above, and any combination
+ of the suffixes 'p' and 'r'. The 'g' suffix toggles the global
+ suffix of the last substitution and resets COUNT to 1. The 'p'
+ suffix toggles the print suffixes of the last substitution. The
+ 'r' suffix causes the regular expression of the last search to be
+ used instead of that of the last substitution (if the search
+ happened after the substitution).
'(.,.)t(.)'
Copies (i.e., transfers) the addressed lines to after the
- right-hand destination address, which may be the address '0'
- (zero). The current address is set to the last line copied.
+ 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 last command 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.
+ 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.
'(1,$)v/RE/COMMAND-LIST'
This is similar to the 'g' command except that it applies
filename is specified, then the default filename is used. The
current address is unchanged.
-'(1,$)w !COMMAND'
- Writes the addressed lines to the standard input of '!COMMAND',
- (see the '!' command below). The default filename and current
- address are 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 prevent the warning to the user if an attempt is
+ made to overwrite or discard the buffer via the 'e' or 'q'
+ commands.
'(1,$)wq FILE'
Writes the addressed lines to FILE, and then executes a 'q'
'(1,$)W FILE'
Appends the addressed lines to the end of FILE. This is similar to
- the 'w' command, expect that the previous contents of file is not
+ the 'w' command, except that the previous contents of file is 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 last line copied.
+ 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 'y', 's', 'j', 'd', or 'c'
+ buffer is overwritten by subsequent 'c', 'd', 'j', 's', or 'y'
commands. The current address is unchanged.
'(.+1)zN'
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 current address
- is set to the last line printed.
+ is set to the address of the last line printed.
'!COMMAND'
- Executes COMMAND via 'sh (1)'. If the first character of COMMAND
- is '!', then it is replaced by text of the previous '!COMMAND'.
- 'ed' does not process COMMAND for backslash ('\') escapes.
- However, an unescaped '%' is replaced by the default filename.
- When the shell returns from execution, a '!' is printed to the
- standard output. The current line is unchanged.
+ Shell escape command. Executes COMMAND via 'sh (1)'. 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, an unescaped '%' is replaced by the default
+ filename. When the shell returns from execution, a '!' is printed
+ to the standard output. The current address is unchanged.
'(.,.)#'
Begins a comment; the rest of the line, up to a newline, is
address is unchanged.
'($)='
- Prints the line number of the addressed line.
+ Prints the line number of the addressed line. The current address
+ is unchanged.
'(.+1)<newline>'
- An address alone prints the addressed line. A <newline> alone is
- equivalent to '+1p'. The current address is set to the address of
- the printed line.
+ Null command. An address alone prints the addressed line. A
+ <newline> alone is equivalent to '+1p'. The current address is set
+ to the address of the printed line.
\1f
modified buffer results in an error. If the command is entered a second
time, it succeeds, but any changes to the buffer are lost.
- Exit status: 0 if no errors occurred; otherwise >0.
-
\1f
File: ed.info, Node: Problems, Next: GNU Free Documentation License, Prev: Diagnostics, Up: Top
\1f
Tag Table:
Node: Top\7f535
-Node: Overview\7f2195
-Node: Introduction to line editing\7f4251
-Node: Invoking ed\7f11470
-Node: Line addressing\7f13270
-Node: Regular expressions\7f16347
-Node: Commands\7f21691
-Node: Limitations\7f32963
-Node: Diagnostics\7f33608
-Node: Problems\7f34309
-Node: GNU Free Documentation License\7f34842
+Node: Overview\7f2198
+Node: Introduction to line editing\7f4254
+Node: Invoking ed\7f11527
+Node: Line addressing\7f13529
+Node: Regular expressions\7f17242
+Node: Commands\7f22586
+Ref: shell escape command\7f36495
+Node: Limitations\7f37517
+Node: Diagnostics\7f38162
+Node: Problems\7f38807
+Node: GNU Free Documentation License\7f39340
\1f
End Tag Table
@finalout
@c %**end of header
-@set UPDATED 24 January 2016
-@set VERSION 1.13
+@set UPDATED 22 February 2017
+@set VERSION 1.14.2
@dircategory Basics
@direntry
@end direntry
@copying
-Copyright @copyright{} 1993, 1994, 2006-2016
+Copyright @copyright{} 1993, 1994, 2006-2017
Free Software Foundation, Inc.
Permission is granted to copy, distribute and/or modify this document
# Move the title to its proper place.
5m0p
Sonnet #50
-# The title is now the first line, and the current line has been
-# set to this line as well.
+# 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.
$
@end example
-When @command{ed} opens a file, the current line is initially set to the
-last line of that file. Similarly, the move command @samp{m} sets the
-current line to the last line moved.
+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
@item -s
@itemx --quiet
@itemx --silent
-Suppresses diagnostics. This should be used if @command{ed}'s standard
+Suppresses diagnostics, the printing of byte counts by @samp{e},
+@samp{E}, @samp{r} and @samp{w} commands, and the @samp{!} prompt after
+a @samp{!} command. This option may be useful if @command{ed}'s standard
input is from a script.
@item -v
@itemx --verbose
-Verbose mode. This may be toggled on and off with the @samp{H} command.
+Verbose mode; prints error explanations. This may be toggled on and off
+with the @samp{H} command.
@end table
+Exit status: 0 if no errors occurred; otherwise >0.
+
@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 last line of the file. In
-general, the current address is set to the last line affected by a
-command.
+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 "before the first line", 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. If only one address is given in a
-range, then the second address is set to the given address. If an
-@var{n}-tuple of addresses is given where @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.
-
-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.
+or a semicolon (@samp{;}). 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. The value of the first address in a range cannot exceed the
+value of the second.
+
+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 @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:
@item ;
The current through last lines in the buffer. This is equivalent to the
-address range @samp{.,$}.
+address range @samp{.;$}.
@item /@var{re}/
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. @samp{//} repeats the last search.
+line, if necessary. A null @var{re} @samp{//} repeats the last search.
@item ?@var{re}?
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. @samp{??} repeats the last search.
+necessary. A null @var{re} @samp{??} repeats the last search.
@item 'x
The apostrophe-x character pair addresses the line previously marked by
@itemize @bullet
@item
-A number adds the indicated number of lines to the address.
-
-@item
@samp{+} or @samp{-} followed by a number adds or subtracts the
indicated number of lines to or from the address.
@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 @var{re}
+fails to find a matching line.
+
@node Regular expressions
@chapter Regular expressions
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.
+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.
An interrupt (typically @key{Control-C}) has the effect of aborting the
current command and returning the editor to command mode.
@table @code
@item (.)a
-Appends text to the buffer after the addressed line, which may be the
-address @samp{0} (zero). Text is entered in input mode. The current
-address is set to last line entered.
+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 appended in their place. Text is entered in input
-mode. The current address is set to last line entered.
+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.
@item (.,.)d
-Deletes the addressed lines from the buffer. If there is a line after
-the deleted range, then the current address is set to this line.
-Otherwise the current address is set to the line before the deleted
-range.
+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.
@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. The current address is set to
-the last line read.
+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.
-@item e !@var{command}
-Edits the standard output of @samp{!@var{command}}, (see the @samp{!}
-command below). The default filename is unchanged. Any lines in the
-buffer are deleted before the output of @var{command} is read. The
-current address is set to the last line read.
+A warning is printed if any changes have been made in the buffer since
+the last @samp{w} command that wrote the entire buffer to a file.
@item E @var{file}
Edits @var{file} unconditionally. This is similar to the @samp{e}
command, except that unwritten changes are discarded without warning.
-The current address is set to the last line read.
@item f @var{file}
Sets the default filename to @var{file}. If @var{file} is not specified,
then the default unescaped filename is printed.
@item (1,$)g/@var{re}/@var{command-list}
-Global command. Applies @var{command-list} to each of the addressed
-lines matching a regular expression @var{re}. The current address is set
-to the line currently matched before @var{command-list} is executed. At
-the end of the @samp{g} command, the current address is set to the last
-line affected by @var{command-list}.
-
-At least the first command of @var{command-list} must appear on the same
-line as the @samp{g} command. 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}. 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 @samp{-G}, then a newline in
-@var{command-list} is equivalent to a @samp{.+1p} command.
+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. 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 first command of @var{command-list} must appear on the same line as
+the @samp{g} command. 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 @samp{-G}, then a newline in @var{command-list} is
+equivalent to a @samp{.+1p} command.
@item (1,$)G/@var{re}/
Interactive global command. Interactively edits the addressed lines
matching a regular expression @var{re}. For each matching line, the line
is printed, the current address is set, and the user is prompted to
-enter a @var{command-list}. At the end of the @samp{G} command, the
-current address is set to the last line affected by (the last)
-@var{command-list}.
+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 a null command list. A single @samp{&}
to aid in debugging.
@item (.)i
-Inserts text in the buffer before the current line. The address @samp{0}
-(zero) is valid for this command; it is equivalent to address @samp{1}.
-Text is entered in input mode. The current address is set to the last
-line entered.
+Inserts text in the buffer before the addressed line. The address
+@samp{0} (zero) is valid for this command; it is equivalent to address
+@samp{1}. 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. The addressed lines are deleted from the
-buffer and replaced by a single line containing their joined text. The
-current address is set to the resultant line.
+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 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.
+or otherwise modified. The current address is unchanged.
@item (.,.)l
-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. The current address is set to the last line
-printed.
+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, which may be the address @samp{0}
-(zero). The current address is set to the new address of the last line
-moved.
+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 moved lines. The current address is set to the new address
+of the last line moved.
@item (.,.)n
-Prints the addressed lines, preceding each line by its line number and a
-@key{tab}. The current address is set to the last line printed.
+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 last line
-printed.
+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 is specified with
off.
@item q
-Quits @command{ed}.
+Quits @command{ed}. A warning is printed if any changes have been made
+in the buffer since the last @samp{w} command that wrote the entire
+buffer to a file.
@item Q
Quits @command{ed} unconditionally. This is similar to the @code{q}
command, except that unwritten changes are discarded without warning.
@item ($)r @var{file}
-Reads @var{file} to 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 current
-address is set to the last line read.
-
-@item ($)r !@var{command}
-Reads to after the addressed line the standard output of
-@samp{!command}, (see the @samp{!} command below). The default filename
-is unchanged. The current address is set to the last line read.
+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.
@item (.,.)s/@var{re}/@var{replacement}/
-@itemx (.,.)s/@var{re}/@var{replacement}/g
-@itemx (.,.)s/@var{re}/@var{replacement}/@var{n}
-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. If the @samp{g} (global) suffix is given, then
-every match is replaced. The @var{n} suffix, where @var{n} is a postive
-number, causes only the @var{n}th match to be replaced. It is an error
-if no substitutions are performed on any of the addressed lines. The
-current address is set to the last line affected.
+Substitute command. 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 @samp{s} command accepts any
+combination of the suffixes @samp{g}, @samp{@var{count}}, @samp{l},
+@samp{n}, and @samp{p}. If the @samp{g} (global) suffix is given, then
+every match is replaced. The @samp{@var{count}} suffix, where
+@var{count} is 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. @samp{l}, @samp{n}, and @samp{p} are the usual print
+suffixes. 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.
@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 one or two of the last delimiters
-is omitted, then the last line affected is printed as if the print
-suffix @samp{p} were specified.
+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 becomes 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 @var{replacement} consists of a
+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. Newlines may be embedded in @var{replacement} if they are escaped
-with a backslash (@samp{\}).
+used.
+
+A line can be split by including a newline escaped with a backslash
+(@samp{\}) in @var{replacement}, except if the @samp{s} command is part
+of a @samp{g} or @samp{v} @var{command-list}, because in this case the
+meaning of the escaped newline becomes ambiguous. Each backslash in
+@var{replacement} removes the special meaning (if any) of the following
+character.
@item (.,.)s
Repeats the last substitution. This form of the @samp{s} command accepts
-a count suffix @var{n}, and any combination of the characters @samp{r},
-@samp{g}, and @samp{p}. If a count suffix @var{n} is given, then only
-the @var{n}th match is replaced. The @samp{r} suffix causes the regular
-expression of the last search to be used instead of the that of the last
-substitution. The @samp{g} suffix toggles the global suffix of the last
-substitution. The @samp{p} suffix toggles the print suffix of the last
-substitution. The current address is set to the last line affected.
+the @samp{g} and @samp{@var{count}} suffixes described above, and any
+combination of the suffixes @samp{p} and @samp{r}. The @samp{g} suffix
+toggles the global suffix of the last substitution and resets
+@var{count} to 1. The @samp{p} suffix toggles the print suffixes of the
+last substitution. The @samp{r} suffix causes the regular expression of
+the last search to be used instead of that of the last substitution (if
+the search happened after the substitution).
@item (.,.)t(.)
Copies (i.e., transfers) the addressed lines to after the right-hand
-destination address, which may be the address @samp{0} (zero). The
-current address is set to the last line copied.
+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 last command 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.
+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.
@item (1,$)v/@var{re}/@var{command-list}
This is similar to the @samp{g} command except that it applies
unchanged. If no filename is specified, then the default filename is
used. The current address is unchanged.
-@item (1,$)w !@var{command}
-Writes the addressed lines to the standard input of
-@samp{!@var{command}}, (see the @samp{!} command below). The default
-filename and current address are 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
+prevent the warning to the user if an attempt is made to overwrite or
+discard the buffer via the @samp{e} or @samp{q} commands.
@item (1,$)wq @var{file}
Writes the addressed lines to @var{file}, and then executes a @samp{q}
@item (1,$)W @var{file}
Appends the addressed lines to the end of @var{file}. This is similar to
-the @samp{w} command, expect that the previous contents of file is not
+the @samp{w} command, except that the previous contents of file is 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 last line copied.
+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{y}, @samp{s}, @samp{j}, @samp{d}, or
-@samp{c} commands. The current address is unchanged.
+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}
Scrolls @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 current address
-is set to the last line printed.
+is set to the address of the last line printed.
+@anchor{shell escape command}
@item !@var{command}
-Executes @var{command} via @command{sh (1)}. If the first character of
-@var{command} is @samp{!}, then it is replaced by text of the previous
-@samp{!@var{command}}. @command{ed} does not process @var{command} for
-backslash (@samp{\}) escapes. However, an unescaped @samp{%} is replaced
-by the default filename. When the shell returns from execution, a
-@samp{!} is printed to the standard output. The current line is
-unchanged.
+Shell escape command. Executes @var{command} via @command{sh (1)}. 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, an unescaped
+@samp{%} is replaced by the default filename. When the shell returns
+from execution, a @samp{!} is printed to the standard output. The
+current address is unchanged.
@item (.,.)#
Begins a comment; the rest of the line, up to a newline, is ignored. If
unchanged.
@item ($)=
-Prints the line number of the addressed line.
+Prints the line number of the addressed line. The current address is
+unchanged.
@item (.+1)@key{newline}
-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.
+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
modified buffer results in an error. If the command is entered a second
time, it succeeds, but any changes to the buffer are lost.
-Exit status: 0 if no errors occurred; otherwise >0.
-
@node Problems
@chapter Reporting bugs
/* Global declarations for the ed editor. */
/* GNU ed - The GNU line editor.
Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 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
typedef enum Bool bool;
#endif
-enum Gflags
+enum Pflags /* print suffixes */
{
- GLB = 0x01, /* global command */
- GLS = 0x02, /* list after command */
- GNP = 0x04, /* enumerate after command */
- GPR = 0x08, /* print after command */
- GSG = 0x10 /* global substitute */
+ GLS = 0x01, /* list after command */
+ GNP = 0x02, /* enumerate after command */
+ GPR = 0x04 /* print after command */
};
/* defined in buffer.c */
bool append_lines( const char ** const ibufpp, const int addr,
- const bool isglobal );
+ 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 );
bool modified( void );
bool move_lines( const int first_addr, const int second_addr, const int addr,
const bool isglobal );
-bool newline_added( void );
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,
- const int addr );
+const char * put_sbuf_line( const char * const buf, const int size );
line_t * search_line_node( const int addr );
void set_binary( void );
void set_current_addr( const int addr );
void set_modified( const bool m );
-void set_newline_added( void );
bool yank_lines( const int from, const int to );
void clear_undo_stack( void );
undo_t * push_undo_atom( const int type, const int from, const int to );
void unset_active_nodes( const line_t * bp, const line_t * const ep );
/* defined in io.c */
-bool display_lines( int from, const int to, const int gflags );
bool get_extended_line( const char ** const ibufpp, int * const lenp,
const bool strip_escaped_newlines );
-const char * get_tty_line( int * const sizep );
+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 );
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_t * const lp );
/* defined in main.c */
bool is_regular_file( const int fd );
/* defined in regex.c */
bool build_active_list( const char ** const ibufpp, const int first_addr,
const int second_addr, const bool match );
-bool extract_subst_tail( const char ** const ibufpp, int * const gflagsp,
- int * const snump, const bool isglobal );
+bool extract_replacement( const char ** const ibufpp, const bool isglobal );
int next_matching_node_addr( const char ** const ibufpp, const bool forward );
-bool new_compiled_pattern( const char ** const ibufpp );
-bool prev_pattern( void );
bool search_and_replace( const int first_addr, const int second_addr,
- const int gflags, const int snum, const bool isglobal );
+ const int snum, const bool isglobal );
+bool set_subst_regex( const char ** const ibufpp );
+bool subst_regex( void );
/* defined in signal.c */
void disable_interrupts( void );
/* global.c: global command routines for the ed line editor */
/* GNU ed - The GNU line editor.
Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 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
/* io.c: i/o routines for the ed line editor */
/* GNU ed - The GNU line editor.
Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 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
#include "ed.h"
+static const line_t * unterminated_line = 0; /* last line has no '\n' */
+int linenum_ = 0; /* script line number */
+
+void reset_unterminated_line( void ) { unterminated_line = 0; }
+
+void unmark_unterminated_line( const line_t * 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() ) ); }
+
+int linenum( void ) { return linenum_; }
+
+
/* print text to stdout */
-static void put_tty_line( const char * p, int len, const int gflags )
+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( gflags & GNP ) { printf( "%d\t", current_addr() ); col = 8; }
+ if( pflags & GNP ) { printf( "%d\t", current_addr() ); col = 8; }
while( --len >= 0 )
{
const unsigned char ch = *p++;
- if( !( gflags & GLS ) ) putchar( ch );
+ if( !( pflags & GLS ) ) putchar( ch );
else
{
if( ++col > window_columns() ) { col = 1; fputs( "\\\n", stdout ); }
}
}
}
- if( !traditional() && ( gflags & GLS ) ) putchar('$');
+ if( !traditional() && ( pflags & GLS ) ) putchar('$');
putchar('\n');
}
/* print a range of lines to stdout */
-bool display_lines( int from, const int to, const int gflags )
+bool print_lines( int from, const int to, const int pflags )
{
line_t * const ep = search_line_node( inc_addr( to ) );
line_t * bp = search_line_node( from );
const char * const s = get_sbuf_line( bp );
if( !s ) return false;
set_current_addr( from++ );
- put_tty_line( s, bp->len, gflags );
+ print_line( s, bp->len, pflags );
bp = bp->q_forw;
}
return true;
/* If *ibufpp contains an escaped newline, get an extended line (one
- with escaped newlines) from stdin */
+ with escaped newlines) from stdin.
+ 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 )
{
while( true )
{
int len2;
- const char * const s = get_tty_line( &len2 );
- if( !s ) return false;
- if( len2 == 0 || s[len2-1] != '\n' )
- { set_error_msg( "Unexpected end-of-file" ); return false; }
+ 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 ) ) return false;
memcpy( buf + len, s, len2 );
len += len2;
/* Read a line of text from stdin.
- Returns pointer to buffer and line size (including trailing newline
- if it exists) */
-const char * get_tty_line( int * const sizep )
+ Incomplete lines (lacking the trailing newline) are discarded.
+ Returns 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;
while( true )
{
const int c = getchar();
- if( !resize_buffer( &buf, &bufsz, i + 2 ) )
- { if( sizep ) *sizep = 0; return 0; }
+ 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 ); if( sizep ) *sizep = 0;
- return 0;
+ clearerr( stdin );
+ *sizep = 0; return 0;
}
if( feof( stdin ) )
{
+ set_error_msg( "Unexpected end-of-file" );
clearerr( stdin );
- buf[i] = 0; if( sizep ) *sizep = i;
+ buf[0] = 0; *sizep = 0; if( i > 0 ) ++linenum_; /* discard line */
return buf;
}
}
else
{
buf[i++] = c; if( !c ) set_binary(); if( c != '\n' ) continue;
- buf[i] = 0; if( sizep ) *sizep = i;
+ ++linenum_; buf[i] = 0; *sizep = i;
return buf;
}
}
Returns pointer to buffer and line size (including trailing newline
if it exists and is not added now) */
static const char * read_stream_line( FILE * const fp, int * const sizep,
- bool * const newline_added_nowp )
+ bool * const newline_addedp )
{
static char * buf = 0;
static int bufsz = 0;
}
else if( i )
{
- buf[i] = '\n'; buf[i+1] = 0; *newline_added_nowp = true;
+ buf[i] = '\n'; buf[i+1] = 0; *newline_addedp = true;
if( !isbinary() ) ++i;
}
}
}
-/* read a stream into the editor buffer; return total size of data read */
+/* read a stream into the editor buffer;
+ return total size of data read, or -1 if error */
static long read_stream( FILE * const fp, const int addr )
{
line_t * lp = search_line_node( addr );
long total_size = 0;
const bool o_isbinary = isbinary();
const bool appended = ( addr == last_addr() );
- bool newline_added_now = false;
+ 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( fp, &size, &newline_added_now );
+ const char * const s = read_stream_line( fp, &size, &newline_added );
if( !s ) return -1;
- if( size > 0 ) total_size += size;
- else break;
+ if( size <= 0 ) break;
+ total_size += size;
disable_interrupts();
- if( !put_sbuf_line( s, size + newline_added_now, current_addr() ) )
+ if( !put_sbuf_line( s, size + newline_added ) )
{ enable_interrupts(); return -1; }
lp = lp->q_forw;
if( up ) up->tail = lp;
}
enable_interrupts();
}
- if( addr && appended && total_size && o_isbinary && newline_added() )
- fputs( "Newline inserted\n", stderr );
- else if( newline_added_now && ( !appended || !isbinary() ) )
- fputs( "Newline appended\n", stderr );
- if( isbinary() && !o_isbinary && newline_added_now && !appended )
+ 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( !total_size ) newline_added_now = true;
- if( appended && newline_added_now ) set_newline_added();
+ 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 */
+/* read a named file/pipe into the buffer; return line count, or -1 if error */
int read_file( const char * const filename, const int addr )
{
FILE * fp;
set_error_msg( "Cannot close input file" );
return -1;
}
- if( !scripted() ) fprintf( stderr, "%lu\n", size );
+ if( !scripted() ) printf( "%lu\n", size );
return current_addr() - addr;
}
char * p = get_sbuf_line( lp );
if( !p ) return -1;
len = lp->len;
- if( from != last_addr() || !isbinary() || !newline_added() )
+ if( from != last_addr() || !isbinary() || !unterminated_last_line() )
p[len++] = '\n';
size += len;
while( --len >= 0 )
set_error_msg( "Cannot close output file" );
return -1;
}
- if( !scripted() ) fprintf( stderr, "%lu\n", size );
+ if( !scripted() ) printf( "%lu\n", size );
return ( from && from <= to ) ? to - from + 1 : 0;
}
/* GNU ed - The GNU line editor.
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 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
static const char * const Program_name = "GNU Ed";
static const char * const program_name = "ed";
-static const char * const program_year = "2016";
+static const char * const program_year = "2017";
static const char * invocation_name = 0;
static bool restricted_ = false; /* if set, run in restricted mode */
-static bool scripted_ = false; /* if set, suppress diagnostics */
+static bool scripted_ = false; /* if set, suppress diagnostics,
+ byte counts and '!' prompt */
static bool traditional_ = false; /* if set, be backwards compatible */
" -l, --loose-exit-status exit with 0 status even if a command fails\n"
" -p, --prompt=STRING use STRING as an interactive prompt\n"
" -r, --restricted run in restricted mode\n"
- " -s, --quiet, --silent suppress diagnostics\n"
- " -v, --verbose be verbose\n"
+ " -s, --quiet, --silent suppress diagnostics, byte counts and '!' prompt\n"
+ " -v, --verbose be verbose; equivalent to the 'H' command\n"
"Start 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 (file\n"
bool may_access_filename( const char * const name )
{
if( restricted_ &&
- ( *name == '!' || !strcmp( name, ".." ) || strchr( name, '/' ) ) )
+ ( *name == '!' || strcmp( name, ".." ) == 0 || strchr( name, '/' ) ) )
{
set_error_msg( "Shell access restricted" );
return false;
return 3;
}
} /* end process options */
+
setlocale( LC_ALL, "" );
if( !init_buffers() ) return 1;
while( argind < ap_arguments( &parser ) )
{
const char * const arg = ap_argument( &parser, argind );
- if( !strcmp( arg, "-" ) ) { scripted_ = true; ++argind; continue; }
+ if( strcmp( arg, "-" ) == 0 ) { scripted_ = true; ++argind; continue; }
if( may_access_filename( arg ) )
{
if( read_file( arg, 0 ) < 0 && is_regular_file( 0 ) )
}
else
{
- fputs( "?\n", stderr );
+ fputs( "?\n", stdout );
if( arg[0] ) set_error_msg( "Invalid filename" );
if( is_regular_file( 0 ) ) return 2;
}
/* GNU ed - The GNU line editor.
Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 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
c -= 'a';
if( c < 0 || c >= 26 )
{ set_error_msg( "Invalid mark character" ); return -1; }
- return get_line_node_addr( mark[c]);
+ return get_line_node_addr( mark[c] );
}
/* Returns pointer to copy of shell command in the command buffer */
static const char * get_shell_command( const char ** const ibufpp )
{
- static char * buf = 0;
+ 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 */
- const char * p; /* substitution char pointer */
- int i = 0, len;
+ int i = 0, len = 0;
+ bool replacement = false;
if( restricted() ) { set_error_msg( "Shell access restricted" ); return 0; }
if( !get_extended_line( ibufpp, &len, true ) ) return 0;
- p = *ibufpp;
if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return 0;
- buf[i++] = '!'; /* prefix command w/ bang */
+ if( **ibufpp != '!' ) buf[i++] = '!'; /* prefix command w/ bang */
+ else
+ {
+ if( shcmdlen <= 0 || ( traditional() && !shcmd[1] ) )
+ { set_error_msg( "No previous command" ); return 0; }
+ memcpy( buf, shcmd, shcmdlen ); /* bufsz >= shcmdlen */
+ i += shcmdlen; ++*ibufpp; replacement = true;
+ }
while( **ibufpp != '\n' )
{
- if( **ibufpp == '!' )
- {
- if( p != *ibufpp )
- {
- if( !resize_buffer( &buf, &bufsz, i + 1 ) ) return 0;
- buf[i++] = *(*ibufpp)++;
- }
- else if( !shcmd || ( traditional() && !*( shcmd + 1 ) ) )
- { set_error_msg( "No previous command" ); return 0; }
- else
- {
- if( !resize_buffer( &buf, &bufsz, i + shcmdlen ) ) return 0;
- for( p = shcmd + 1; p < shcmd + shcmdlen; ) buf[i++] = *p++;
- p = (*ibufpp)++;
- }
- }
- else if( **ibufpp == '%' )
+ if( **ibufpp == '%' )
{
+ const char * p;
if( !def_filename[0] )
{ set_error_msg( "No current filename" ); return 0; }
p = strip_escapes( def_filename );
len = strlen( p );
if( !resize_buffer( &buf, &bufsz, i + len ) ) return 0;
- while( len-- ) buf[i++] = *p++;
- p = (*ibufpp)++;
+ memcpy( buf + i, p, len );
+ i += len; ++*ibufpp; replacement = true;
}
else
{
if( !resize_buffer( &buf, &bufsz, i + 2 ) ) return 0;
- buf[i++] = **ibufpp;
- if( *(*ibufpp)++ == '\\' ) buf[i++] = *(*ibufpp)++;
+ if( ( buf[i++] = *(*ibufpp)++ ) == '\\' ) buf[i++] = *(*ibufpp)++;
}
}
while( **ibufpp == '\n' ) ++*ibufpp; /* skip newline */
if( !resize_buffer( &shcmd, &shcmdsz, i + 1 ) ) return 0;
memcpy( shcmd, buf, i );
- shcmdlen = i; shcmd[i] = 0;
- if( *p == '!' || *p == '%' ) printf( "%s\n", shcmd + 1 );
+ shcmd[i] = 0; shcmdlen = i;
+ if( replacement ) printf( "%s\n", shcmd + 1 );
return shcmd;
}
/* Returns pointer to copy of filename in the command buffer */
-static const char * get_filename( const char ** const ibufpp )
+static const char * get_filename( const char ** const ibufpp,
+ const bool traditional_f_command )
{
static char * buf = 0;
static int bufsz = 0;
int size = 0;
if( !get_extended_line( ibufpp, &size, true ) ) return 0;
if( **ibufpp == '!' )
- {
- ++*ibufpp;
- return get_shell_command( ibufpp );
- }
+ { ++*ibufpp; return get_shell_command( ibufpp ); }
else if( size > pmax )
{ set_error_msg( "Filename too long" ); return 0; }
}
- else if( !traditional() && !def_filename[0] )
+ else if( !traditional_f_command && !def_filename[0] )
{ set_error_msg( "No current filename" ); return 0; }
if( !resize_buffer( &buf, &bufsz, pmax + 1 ) ) return 0;
for( n = 0; **ibufpp != '\n'; ++n, ++*ibufpp ) buf[n] = **ibufpp;
static void invalid_address( void ) { set_error_msg( "Invalid address" ); }
-/* return the next line address in the command buffer */
-static int next_addr( const char ** const ibufpp, int * const addr_cnt )
+/* Get line addresses from the command buffer until an invalid address
+ is seen. Returns 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 )
{
- const char * const s = *ibufpp = skip_blanks( *ibufpp );
- int addr = current_addr();
bool first = true; /* true == addr, false == offset */
+ first_addr = second_addr = -1; /* set to undefined */
+ *ibufpp = skip_blanks( *ibufpp );
+
while( true )
{
int n;
const unsigned char ch = **ibufpp;
if( isdigit( ch ) )
{
- if( !first ) { invalid_address(); return -2; };
- if( !parse_int( &addr, *ibufpp, ibufpp ) ) return -2;
+ if( !parse_int( &n, *ibufpp, ibufpp ) ) return -1;
+ if( first ) { first = false; second_addr = n; } else second_addr += n;
}
else switch( ch )
{
- case '+':
case '\t':
- case ' ':
- case '-': *ibufpp = skip_blanks( ++*ibufpp );
- if( isdigit( (unsigned char)**ibufpp ) )
+ 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, ibufpp ) ) return -2;
- addr += ( ( ch == '-' ) ? -n : n );
+ if( !parse_int( &n, *ibufpp, ibufpp ) ) return -1;
+ second_addr += n;
}
- else if( ch == '+' ) ++addr;
- else if( ch == '-' ) --addr;
+ else { ++*ibufpp;
+ if( ch == '+' ) ++second_addr; else --second_addr; }
break;
case '.':
- case '$': if( !first ) { invalid_address(); return -2; };
- ++*ibufpp;
- addr = ( ( ch == '.' ) ? current_addr() : last_addr() );
+ 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 -2; };
- addr = next_matching_node_addr( ibufpp, ch == '/' );
- if( addr < 0 ) return -2;
- if( ch == **ibufpp ) ++*ibufpp;
- break;
- case '\'':if( !first ) { invalid_address(); return -2; };
- ++*ibufpp;
- addr = get_marked_node_addr( *(*ibufpp)++ );
- if( addr < 0 ) return -2;
+ case '?': if( !first ) { invalid_address(); return -1; };
+ second_addr = next_matching_node_addr( ibufpp, ch == '/' );
+ if( second_addr < 0 ) return -1;
+ if( ch == **ibufpp ) ++*ibufpp; /* remove delimiter */
+ 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 )
{
- ++*ibufpp; ++*addr_cnt;
- second_addr = ( ( ch == ';' ) ? current_addr() : 1 );
- addr = last_addr();
- break;
- } /* FALL THROUGH */
- default : if( *ibufpp == s ) return -1; /* EOF */
- if( addr < 0 || addr > last_addr() )
- { invalid_address(); return -2; }
- ++*addr_cnt; return addr;
+ if( first_addr < 0 )
+ { first_addr = ( ( ch == ';' ) ? current_addr() : 1 );
+ second_addr = last_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;
+ }
}
- first = false;
- }
- }
-
-
-/* get line addresses from the command buffer until an invalid address
- is seen. Returns the number of addresses read */
-static int extract_addr_range( const char ** const ibufpp )
- {
- int addr;
- int addr_cnt = 0;
-
- first_addr = second_addr = current_addr();
- while( true )
- {
- addr = next_addr( ibufpp, &addr_cnt );
- if( addr < 0 ) break;
- first_addr = second_addr; second_addr = addr;
- if( **ibufpp != ',' && **ibufpp != ';' ) break;
- if( **ibufpp == ';' ) set_current_addr( addr );
- ++*ibufpp;
}
- if( addr_cnt == 1 || second_addr != addr ) first_addr = second_addr;
- return ( ( addr != -2 ) ? addr_cnt : -1 );
}
{
const int old1 = first_addr;
const int old2 = second_addr;
- int addr_cnt = extract_addr_range( ibufpp );
+ int addr_cnt = extract_addresses( ibufpp );
if( addr_cnt < 0 ) return false;
if( traditional() && addr_cnt == 0 )
}
-/* return true if address range is valid */
+/* 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( 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;
}
-
-/* return true if current address is valid */
-static bool check_current_addr( const int addr_cnt )
+/* 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;
+ }
+
-/* verify the command suffix in the command buffer */
+/* verify the command suffixes in the command buffer */
static bool get_command_suffix( const char ** const ibufpp,
- int * const gflagsp )
+ int * const pflagsp, int * const snump )
{
+ bool nos_or_rep = !snump; /* not s command or repeated g/count */
+ bool error = false;
while( true )
{
- const char ch = **ibufpp;
- if( ch == 'l' ) *gflagsp |= GLS;
- else if( ch == 'n' ) *gflagsp |= GNP;
- else if( ch == 'p' ) *gflagsp |= GPR;
+ const unsigned char ch = **ibufpp;
+ if( ch >= '1' && ch <= '9' )
+ {
+ int n = 0;
+ if( nos_or_rep || !parse_int( &n, *ibufpp, ibufpp ) || n <= 0 )
+ { error = true; break; }
+ nos_or_rep = true; *snump = n; continue;
+ }
+ else if( ch == 'g' )
+ { if( nos_or_rep ) break; else { nos_or_rep = true; *snump = 0; } }
+ else if( ch == 'l' ) { if( *pflagsp & GLS ) break; else *pflagsp |= GLS; }
+ else if( ch == 'n' ) { if( *pflagsp & GNP ) break; else *pflagsp |= GNP; }
+ else if( ch == 'p' ) { if( *pflagsp & GPR ) break; else *pflagsp |= GPR; }
else break;
++*ibufpp;
}
- if( *(*ibufpp)++ != '\n' )
+ if( error || *(*ibufpp)++ != '\n' )
{ set_error_msg( "Invalid command suffix" ); return false; }
return true;
}
}
-static bool command_s( const char ** const ibufpp, int * const gflagsp,
+static bool command_s( const char ** const ibufpp, int * const pflagsp,
const int addr_cnt, const bool isglobal )
{
- static int gflags = 0;
- static int snum = 0;
+ static int pflags = 0; /* print suffixes */
+ static int gmask = GPR; /* the print suffixes to be toggled */
+ static int snum = 1; /* > 0 count, <= 0 global substitute */
enum Sflags {
SGG = 0x01, /* complement previous global substitute suffix */
SGP = 0x02, /* complement previous print suffix */
- SGR = 0x04, /* use last regex instead of last pat */
- SGF = 0x08 /* repeat last substitution */
- } sflags = 0;
+ SGR = 0x04, /* use regex of last search (if newer) */
+ SGF = 0x08
+ } sflags = 0; /* if sflags != 0, repeat last substitution */
+ if( !check_addr_range2( addr_cnt ) ) return false;
do {
- if( isdigit( (unsigned char)**ibufpp ) )
+ bool error = false;
+ if( **ibufpp >= '1' && **ibufpp <= '9' )
{
- if( !parse_int( &snum, *ibufpp, ibufpp ) ) return false;
- sflags |= SGF; gflags &= ~GSG; /* override GSG */
+ int n = 0;
+ if( ( sflags & SGG ) || !parse_int( &n, *ibufpp, ibufpp ) || n <= 0 )
+ error = true;
+ else
+ { sflags |= SGG; snum = n; }
}
else switch( **ibufpp )
{
case '\n':sflags |= SGF; break;
- case 'g': sflags |= SGG; ++*ibufpp; break;
- case 'p': sflags |= SGP; ++*ibufpp; break;
- case 'r': sflags |= SGR; ++*ibufpp; break;
- default : if( sflags )
- { set_error_msg( "Invalid command suffix" ); return false; }
+ case 'g': if( sflags & SGG ) error = true;
+ else { sflags |= SGG; snum = !snum; ++*ibufpp; }
+ break;
+ case 'p': if( sflags & SGP ) error = true;
+ else { sflags |= SGP; ++*ibufpp; } break;
+ case 'r': if( sflags & SGR ) error = true;
+ else { sflags |= SGR; ++*ibufpp; } break;
+ default : if( sflags ) error = true;
}
+ if( error ) { set_error_msg( "Invalid command suffix" ); return false; }
}
while( sflags && **ibufpp != '\n' );
- if( sflags && !prev_pattern() )
+ if( sflags && !subst_regex() )
{ set_error_msg( "No previous substitution" ); return false; }
- if( sflags & SGG ) snum = 0; /* override numeric arg */
- if( **ibufpp != '\n' && (*ibufpp)[1] == '\n' )
- { set_error_msg( "Invalid pattern delimiter" ); return false; }
- if( ( !sflags || ( sflags & SGR ) ) && !new_compiled_pattern( ibufpp ) )
- return false;
- if( !sflags && !extract_subst_tail( ibufpp, &gflags, &snum, isglobal ) )
+ if( ( !sflags || ( sflags & SGR ) ) && !set_subst_regex( ibufpp ) )
return false;
- if( isglobal ) gflags |= GLB;
- else gflags &= ~GLB;
- if( sflags & SGG ) gflags ^= GSG;
- if( sflags & SGP ) { gflags ^= GPR; gflags &= ~( GLS | GNP ); }
- switch( **ibufpp )
+ if( !sflags )
{
- case 'l': gflags |= GLS; ++*ibufpp; break;
- case 'n': gflags |= GNP; ++*ibufpp; break;
- case 'p': gflags |= GPR; ++*ibufpp; break;
+ const char delimiter = **ibufpp;
+ pflags = 0; snum = 1;
+ if( !extract_replacement( ibufpp, isglobal ) ) return false;
+ if( **ibufpp == '\n' ) pflags = GPR; /* omitted last delimiter */
+ else if( **ibufpp == delimiter ) ++*ibufpp; /* skip delimiter */
+ if( !get_command_suffix( ibufpp, &pflags, &snum ) ) return false;
+ gmask = pflags & ( GPR | GLS | GNP ); if( gmask == 0 ) gmask = GPR;
}
- if( !check_current_addr( addr_cnt ) ||
- !get_command_suffix( ibufpp, gflagsp ) ) return false;
+ else if( sflags & SGP ) pflags ^= gmask;
+ *pflagsp = pflags;
if( !isglobal ) clear_undo_stack();
- if( !search_and_replace( first_addr, second_addr, gflags, snum, isglobal ) )
- return false;
- if( ( gflags & ( GPR | GLS | GNP ) ) &&
- !display_lines( current_addr(), current_addr(), gflags ) )
+ if( !search_and_replace( first_addr, second_addr, snum, isglobal ) )
return false;
return true;
}
-static bool exec_global( const char ** const ibufpp, const int gflags,
+static bool 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 int prev_status,
const bool isglobal )
{
- const char * fnp;
- int gflags = 0;
+ const char * fnp; /* filename */
+ int pflags = 0; /* print suffixes */
int addr, c, n;
- const int addr_cnt = extract_addr_range( ibufpp );
+ const int addr_cnt = extract_addresses( ibufpp );
if( addr_cnt < 0 ) return ERR;
*ibufpp = skip_blanks( *ibufpp );
c = *(*ibufpp)++;
switch( c )
{
- case 'a': if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ case 'a': if( !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( !isglobal ) clear_undo_stack();
- if( !append_lines( ibufpp, second_addr, isglobal ) ) return ERR;
+ if( !append_lines( ibufpp, second_addr, false, isglobal ) )
+ return ERR;
break;
case 'c': if( first_addr == 0 ) first_addr = 1;
if( second_addr == 0 ) second_addr = 1;
- if( !check_current_addr( addr_cnt ) ||
- !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ if( !check_addr_range2( addr_cnt ) ||
+ !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( !isglobal ) clear_undo_stack();
if( !delete_lines( first_addr, second_addr, isglobal ) ||
- !append_lines( ibufpp, current_addr(), isglobal ) ) return ERR;
+ !append_lines( ibufpp, current_addr(),
+ current_addr() >= first_addr, isglobal ) )
+ return ERR;
break;
- case 'd': if( !check_current_addr( addr_cnt ) ||
- !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ case 'd': if( !check_addr_range2( addr_cnt ) ||
+ !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( !isglobal ) clear_undo_stack();
if( !delete_lines( first_addr, second_addr, isglobal ) ) return ERR;
- inc_current_addr();
break;
- case 'e': if( modified() && !scripted() && prev_status != EMOD )
- return EMOD; /* fall through */
+ case 'e': if( modified() && prev_status != EMOD ) return EMOD;
+ /* fall through */
case 'E': if( unexpected_address( addr_cnt ) ||
unexpected_command_suffix( **ibufpp ) ) return ERR;
- fnp = get_filename( ibufpp );
+ fnp = get_filename( ibufpp, false );
if( !fnp || !delete_lines( 1, last_addr(), isglobal ) ||
!close_sbuf() ) return ERR;
if( !open_sbuf() ) return FATAL;
if( fnp[0] && fnp[0] != '!' ) set_def_filename( fnp );
- if( traditional() && !fnp[0] && !def_filename[0] )
- { set_error_msg( "No current filename" ); return ERR; }
if( read_file( fnp[0] ? fnp : def_filename, 0 ) < 0 )
return ERR;
reset_undo_state(); set_modified( false );
break;
case 'f': if( unexpected_address( addr_cnt ) ||
unexpected_command_suffix( **ibufpp ) ) return ERR;
- fnp = get_filename( ibufpp );
+ fnp = get_filename( ibufpp, traditional() );
if( !fnp ) return ERR;
if( fnp[0] == '!' )
{ set_error_msg( "Invalid redirection" ); return ERR; }
!build_active_list( ibufpp, first_addr, second_addr, n ) )
return ERR;
n = ( c == 'G' || c == 'V' ); /* interactive */
- if( ( n && !get_command_suffix( ibufpp, &gflags ) ) ||
- !exec_global( ibufpp, gflags, n ) )
+ if( ( n && !get_command_suffix( ibufpp, &pflags, 0 ) ) ||
+ !exec_global( ibufpp, pflags, n ) )
return ERR;
break;
case 'h':
case 'H': if( unexpected_address( addr_cnt ) ||
- !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( c == 'H' ) verbose = !verbose;
if( ( c == 'h' || verbose ) && errmsg[0] )
- fprintf( stderr, "%s\n", errmsg );
+ printf( "%s\n", errmsg );
break;
- case 'i': if( second_addr == 0 ) second_addr = 1;
- if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ case 'i': if( !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( !isglobal ) clear_undo_stack();
- if( !append_lines( ibufpp, second_addr - 1, isglobal ) )
+ 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, &gflags ) ) return ERR;
+ !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( !isglobal ) clear_undo_stack();
- if( first_addr != second_addr &&
+ 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, &gflags ) ||
+ if( !get_command_suffix( ibufpp, &pflags, 0 ) ||
!mark_line_node( search_line_node( second_addr ), n ) )
return ERR;
break;
case 'l':
case 'n':
case 'p': if( c == 'l' ) n = GLS; else if( c == 'n' ) n = GNP; else n = GPR;
- if( !check_current_addr( addr_cnt ) ||
- !get_command_suffix( ibufpp, &gflags ) ||
- !display_lines( first_addr, second_addr, gflags | n ) )
+ if( !check_addr_range2( addr_cnt ) ||
+ !get_command_suffix( ibufpp, &pflags, 0 ) ||
+ !print_lines( first_addr, second_addr, pflags | n ) )
return ERR;
- gflags = 0;
+ pflags = 0;
break;
- case 'm': if( !check_current_addr( addr_cnt ) ||
+ 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, &gflags ) ) return ERR;
+ if( !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( !isglobal ) clear_undo_stack();
if( !move_lines( first_addr, second_addr, addr, isglobal ) )
return ERR;
case 'P':
case 'q':
case 'Q': if( unexpected_address( addr_cnt ) ||
- !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( c == 'P' ) prompt_on = !prompt_on;
- else if( modified() && !scripted() && c == 'q' &&
- prev_status != EMOD ) return EMOD;
+ else if( c == 'q' && modified() && prev_status != EMOD )
+ return EMOD;
else return QUIT;
break;
case 'r': if( unexpected_command_suffix( **ibufpp ) ) return ERR;
if( addr_cnt == 0 ) second_addr = last_addr();
- fnp = get_filename( ibufpp );
+ fnp = get_filename( ibufpp, false );
if( !fnp ) return ERR;
- if( !isglobal ) clear_undo_stack();
if( !def_filename[0] && fnp[0] != '!' ) set_def_filename( fnp );
- if( traditional() && !fnp[0] && !def_filename[0] )
- { set_error_msg( "No current filename" ); return ERR; }
+ if( !isglobal ) clear_undo_stack();
addr = read_file( fnp[0] ? fnp : def_filename, second_addr );
if( addr < 0 ) return ERR;
if( addr ) set_modified( true );
break;
- case 's': if( !command_s( ibufpp, &gflags, addr_cnt, isglobal ) )
+ case 's': if( !command_s( ibufpp, &pflags, addr_cnt, isglobal ) )
return ERR;
break;
- case 't': if( !check_current_addr( addr_cnt ) ||
+ case 't': if( !check_addr_range2( addr_cnt ) ||
!get_third_addr( ibufpp, &addr ) ||
- !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ !get_command_suffix( ibufpp, &pflags, 0 ) ) 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, &gflags ) ||
+ !get_command_suffix( ibufpp, &pflags, 0 ) ||
!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 );
+ 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 );
- if( traditional() && !fnp[0] && !def_filename[0] )
- { set_error_msg( "No current filename" ); 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() ) set_modified( false );
- else if( modified() && !scripted() && n == 'q' &&
- prev_status != EMOD ) return EMOD;
+ if( addr == last_addr() && fnp[0] != '!' ) set_modified( false );
+ else if( n == 'q' && modified() && prev_status != EMOD )
+ return EMOD;
if( n == 'q' || n == 'Q' ) return QUIT;
break;
- case 'x': if( second_addr < 0 || last_addr() < second_addr )
+ case 'x': if( second_addr < 0 || second_addr > last_addr() )
{ invalid_address(); return ERR; }
- if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ if( !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
if( !isglobal ) clear_undo_stack();
if( !put_lines( second_addr ) ) return ERR;
break;
- case 'y': if( !check_current_addr( addr_cnt ) ||
- !get_command_suffix( ibufpp, &gflags ) ||
+ case 'y': if( !check_addr_range2( addr_cnt ) ||
+ !get_command_suffix( ibufpp, &pflags, 0 ) ||
!yank_lines( first_addr, second_addr ) ) return ERR;
break;
- case 'z': first_addr = 1;
- if( !check_addr_range( first_addr, current_addr() +
- ( traditional() || !isglobal ), addr_cnt ) )
+ case 'z': if( !check_second_addr( current_addr() + !isglobal, addr_cnt ) )
return ERR;
if( **ibufpp > '0' && **ibufpp <= '9' )
{ if( parse_int( &n, *ibufpp, ibufpp ) ) set_window_lines( n );
else return ERR; }
- if( !get_command_suffix( ibufpp, &gflags ) ||
- !display_lines( second_addr,
+ if( !get_command_suffix( ibufpp, &pflags, 0 ) ||
+ !print_lines( second_addr,
min( last_addr(), second_addr + window_lines() - 1 ),
- gflags ) )
+ pflags ) )
return ERR;
- gflags = 0;
+ pflags = 0;
break;
- case '=': if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
+ case '=': if( !get_command_suffix( ibufpp, &pflags, 0 ) ) return ERR;
printf( "%d\n", addr_cnt ? second_addr : last_addr() );
break;
case '!': if( unexpected_address( addr_cnt ) ) return ERR;
{ set_error_msg( "Can't create shell process" ); return ERR; }
if( !scripted() ) fputs( "!\n", stdout );
break;
- case '\n': first_addr = 1;
- if( !check_addr_range( first_addr, current_addr() +
- ( traditional() || !isglobal ), addr_cnt ) ||
- !display_lines( second_addr, second_addr, 0 ) )
+ 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' ) ;
break;
default : set_error_msg( "Unknown command" ); return ERR;
}
- if( gflags && !display_lines( current_addr(), current_addr(), gflags ) )
+ 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; return false if error */
-static bool exec_global( const char ** const ibufpp, const int gflags,
+static bool exec_global( const char ** const ibufpp, const int pflags,
const bool interactive )
{
static char * buf = 0;
if( !interactive )
{
- if( traditional() && !strcmp( *ibufpp, "\n" ) )
+ if( traditional() && strcmp( *ibufpp, "\n" ) == 0 )
cmd = "p\n"; /* null cmd_list == 'p' */
else
{
if( interactive )
{
/* print current_addr; get a command in global syntax */
- int len;
- if( !display_lines( current_addr(), current_addr(), gflags ) )
+ int len = 0;
+ if( !print_lines( current_addr(), current_addr(), pflags ) )
return false;
- do { *ibufpp = get_tty_line( &len ); }
- while( *ibufpp && len > 0 && (*ibufpp)[len-1] != '\n' );
- if( !*ibufpp ) return false;
- if( len == 0 )
- { set_error_msg( "Unexpected end-of-file" ); return false; }
- if( len == 1 && !strcmp( *ibufpp, "\n" ) ) continue;
- if( len == 2 && !strcmp( *ibufpp, "&\n" ) )
+ *ibufpp = get_stdin_line( &len );
+ if( !*ibufpp ) return false; /* error */
+ if( len <= 0 ) return false; /* EOF */
+ if( len == 1 && strcmp( *ibufpp, "\n" ) == 0 ) continue;
+ if( len == 2 && strcmp( *ibufpp, "&\n" ) == 0 )
{ if( !cmd ) { set_error_msg( "No previous command" ); return false; } }
else
{
}
+static void script_error( void )
+ {
+ if( verbose ) fprintf( stderr, "script, line %d: %s\n", linenum(), errmsg );
+ }
+
+
int main_loop( const bool loose )
{
extern jmp_buf jmp_state;
const char * ibufp; /* pointer to command buffer */
volatile int err_status = 0; /* program exit status */
- volatile int linenum = 0; /* script line number */
- int len, status;
+ int len = 0, status;
disable_interrupts();
set_signals();
status = setjmp( jmp_state );
if( !status ) enable_interrupts();
- else { status = -1; fputs( "\n?\n", stderr ); set_error_msg( "Interrupt" ); }
+ else { status = -1; fputs( "\n?\n", stdout ); set_error_msg( "Interrupt" ); }
while( true )
{
- fflush( stdout );
- if( status < 0 && verbose )
- { fprintf( stderr, "%s\n", errmsg ); fflush( stderr ); }
+ 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_tty_line( &len );
- if( !ibufp ) return err_status;
- if( !len )
+ ibufp = get_stdin_line( &len );
+ if( !ibufp ) return 2; /* an error happened */
+ if( len <= 0 ) /* EOF on stdin ('q') */
{
- if( !modified() || scripted() ) return err_status;
- fputs( "?\n", stderr ); set_error_msg( "Warning: buffer modified" );
- if( is_regular_file( 0 ) )
- {
- if( verbose ) fprintf( stderr, "script, line %d: %s\n", linenum, errmsg );
- return 2;
- }
- set_modified( false ); status = EMOD; continue;
+ if( !modified() || status == EMOD ) status = QUIT;
+ else { status = EMOD; if( !loose ) err_status = 2; }
}
- else if( ibufp[len-1] != '\n' ) /* discard line */
- { set_error_msg( "Unexpected end-of-file" ); status = ERR; continue; }
- else ++linenum;
- status = exec_command( &ibufp, status, false );
+ else status = exec_command( &ibufp, status, false );
if( status == 0 ) continue;
if( status == QUIT ) return err_status;
- if( status == EMOD )
- {
- fputs( "?\n", stderr ); /* give warning */
- set_error_msg( "Warning: buffer modified" );
- if( is_regular_file( 0 ) )
- {
- if( verbose )
- fprintf( stderr, "script, line %d: %s\n", linenum, errmsg );
- return 1;
- }
- }
- else if( status == FATAL )
- {
- if( verbose )
- {
- if( is_regular_file( 0 ) )
- fprintf( stderr, "script, line %d: %s\n", linenum, errmsg );
- else fprintf( stderr, "%s\n", errmsg );
- }
- return 1;
- }
- else
- {
- fputs( "?\n", stderr ); /* give warning */
- if( is_regular_file( 0 ) )
- {
- if( verbose )
- fprintf( stderr, "script, line %d: %s\n", linenum, errmsg );
- return 1;
- }
- }
- if( !loose ) err_status = 1;
+ fputs( "?\n", stdout ); /* give warning */
+ if( !loose && err_status == 0 ) err_status = 1;
+ if( status == EMOD ) set_error_msg( "Warning: buffer modified" );
+ if( is_regular_file( 0 ) )
+ { script_error(); return ( ( status == FATAL ) ? 1 : err_status ); }
+ if( status == FATAL )
+ { if( verbose ) { printf( "%s\n", errmsg ); } return 1; }
}
}
/* regex.c: regular expression interface routines for the ed line editor. */
/* GNU ed - The GNU line editor.
Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 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
#include "ed.h"
-static regex_t * global_pat = 0;
-static bool patlock = false; /* if set, pattern not freed by get_compiled_pattern */
+static regex_t * subst_regex_ = 0; /* regex of previous substitution */
-static char * stbuf = 0; /* substitution template buffer */
-static int stbufsz = 0; /* substitution template buffer size */
-static int stlen = 0; /* substitution template length */
+static char * rbuf = 0; /* replacement buffer */
+static int rbufsz = 0; /* replacement buffer size */
+static int rlen = 0; /* replacement length */
-static char * rbuf = 0; /* replace_matching_text buffer */
-static int rbufsz = 0; /* replace_matching_text buffer size */
-
-bool prev_pattern( void ) { return global_pat != 0; }
+bool subst_regex( void ) { return subst_regex_ != 0; }
/* translate characters in a string */
}
-/* return pointer to compiled pattern from command buffer */
-static regex_t * get_compiled_pattern( const char ** const ibufpp )
+/* 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 bool test_delimiter )
{
+ static regex_t store[2]; /* space for two compiled regexes */
static regex_t * exp = 0;
- const char * exps;
+ const char * pat;
const char delimiter = **ibufpp;
int n;
if( delimiter == ' ' )
{ set_error_msg( "Invalid pattern delimiter" ); return 0; }
- if( delimiter == '\n' || *++*ibufpp == '\n' || **ibufpp == delimiter )
+ if( delimiter == '\n' || *++*ibufpp == delimiter ||
+ ( **ibufpp == '\n' && !test_delimiter ) )
{
if( !exp ) set_error_msg( "No previous pattern" );
return exp;
}
- exps = extract_pattern( ibufpp, delimiter );
- if( !exps ) return 0;
- /* buffer alloc'd && not reserved */
- if( exp && !patlock ) regfree( exp );
- else
- {
- exp = (regex_t *) malloc( sizeof (regex_t) );
- if( !exp )
- {
- show_strerror( 0, errno );
- set_error_msg( "Memory exhausted" );
- return 0;
- }
- }
- patlock = false;
- n = regcomp( exp, exps, 0 );
+ pat = extract_pattern( ibufpp, delimiter );
+ if( !pat ) return 0;
+ if( test_delimiter && delimiter != **ibufpp )
+ { set_error_msg( "Missing pattern delimiter" ); return 0; }
+ /* exp compiled && not copied */
+ if( exp && exp != subst_regex_ ) regfree( exp );
+ else exp = ( &store[0] != subst_regex_ ) ? &store[0] : &store[1];
+ n = regcomp( exp, pat, 0 );
if( n )
{
char buf[80];
regerror( n, exp, buf, sizeof buf );
set_error_msg( buf );
- free( exp );
exp = 0;
}
return exp;
}
-/* add line matching a pattern to the global-active list */
+bool set_subst_regex( const char ** const ibufpp )
+ {
+ regex_t * exp;
+
+ disable_interrupts();
+ exp = get_compiled_regex( ibufpp, true );
+ if( exp && exp != subst_regex_ )
+ {
+ if( subst_regex_ ) regfree( subst_regex_ );
+ subst_regex_ = exp;
+ }
+ enable_interrupts();
+ return ( exp ? true : false );
+ }
+
+
+/* add line 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 )
{
- const regex_t * pat;
+ const regex_t * exp;
const line_t * lp;
int addr;
const char delimiter = **ibufpp;
if( delimiter == ' ' || delimiter == '\n' )
{ set_error_msg( "Invalid pattern delimiter" ); return false; }
- pat = get_compiled_pattern( ibufpp );
- if( !pat ) return false;
+ exp = get_compiled_regex( ibufpp, false );
+ if( !exp ) return false;
if( **ibufpp == delimiter ) ++*ibufpp;
clear_active_list();
lp = search_line_node( first_addr );
char * const s = get_sbuf_line( lp );
if( !s ) return false;
if( isbinary() ) nul_to_newline( s, lp->len );
- if( !regexec( pat, s, 0, 0, 0 ) == match && !set_active_node( lp ) )
+ if( match == !regexec( exp, s, 0, 0, 0 ) && !set_active_node( lp ) )
return false;
}
return true;
}
-/* return pointer to copy of substitution template in the command buffer */
-static char * extract_subst_template( const char ** const ibufpp,
- const bool isglobal )
- {
- int i = 0, n = 0;
- char c;
- const char delimiter = **ibufpp;
-
- ++*ibufpp;
- if( **ibufpp == '%' && (*ibufpp)[1] == delimiter )
- {
- ++*ibufpp;
- if( !stbuf ) set_error_msg( "No previous substitution" );
- return stbuf;
- }
- while( **ibufpp != delimiter )
- {
- if( !resize_buffer( &stbuf, &stbufsz, i + 2 ) ) return 0;
- c = stbuf[i++] = *(*ibufpp)++;
- if( c == '\n' && **ibufpp == 0 ) { --i, --*ibufpp; break; }
- if( c == '\\' && ( stbuf[i++] = *(*ibufpp)++ ) == '\n' && !isglobal )
- {
- while( ( *ibufpp = get_tty_line( &n ) ) &&
- ( n == 0 || ( n > 0 && (*ibufpp)[n-1] != '\n' ) ) )
- clearerr( stdin );
- if( !*ibufpp ) return 0;
- }
- }
- if( !resize_buffer( &stbuf, &stbufsz, i + 1 ) ) return 0;
- stbuf[stlen = i] = 0;
- return stbuf;
- }
-
-
-/* extract substitution tail from the command buffer */
-bool extract_subst_tail( const char ** const ibufpp, int * const gflagsp,
- int * const snump, const bool isglobal )
- {
- const char delimiter = **ibufpp;
-
- *gflagsp = *snump = 0;
- if( delimiter == '\n' ) { stlen = 0; *gflagsp = GPR; return true; }
- if( !extract_subst_template( ibufpp, isglobal ) ) return false;
- if( **ibufpp == '\n' ) { *gflagsp = GPR; return true; }
- if( **ibufpp == delimiter ) ++*ibufpp;
- if( **ibufpp >= '1' && **ibufpp <= '9' )
- return parse_int( snump, *ibufpp, ibufpp );
- if( **ibufpp == 'g' ) { ++*ibufpp; *gflagsp = GSG; }
- return true;
- }
-
-
-/* return the address of the next line matching a pattern in a given
- direction. wrap around begin/end of editor buffer if necessary */
+/* 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 )
{
- const regex_t * const pat = get_compiled_pattern( ibufpp );
+ const regex_t * const exp = get_compiled_regex( ibufpp, false );
int addr = current_addr();
- if( !pat ) return -1;
+ if( !exp ) return -1;
do {
addr = ( forward ? inc_addr( addr ) : dec_addr( addr ) );
if( addr )
char * const s = get_sbuf_line( lp );
if( !s ) return -1;
if( isbinary() ) nul_to_newline( s, lp->len );
- if( !regexec( pat, s, 0, 0, 0 ) ) return addr;
+ if( !regexec( exp, s, 0, 0, 0 ) ) return addr;
}
}
while( addr != current_addr() );
}
-bool new_compiled_pattern( const char ** const ibufpp )
+/* extract substitution replacement from the command buffer */
+bool extract_replacement( const char ** const ibufpp, const bool isglobal )
{
- regex_t * tpat;
+ static char * buf = 0; /* temporary buffer */
+ static int bufsz = 0;
+ int i = 0;
+ const char delimiter = **ibufpp;
- disable_interrupts();
- tpat = get_compiled_pattern( ibufpp );
- if( tpat && tpat != global_pat )
+ if( delimiter == '\n' )
+ { set_error_msg( "Missing pattern delimiter" ); return false; }
+ ++*ibufpp;
+ if( **ibufpp == '%' && ( (*ibufpp)[1] == delimiter || (*ibufpp)[1] == '\n' ) )
+ {
+ ++*ibufpp;
+ if( !rbuf ) { set_error_msg( "No previous substitution" ); return false; }
+ return true;
+ }
+ while( **ibufpp != delimiter )
{
- if( global_pat ) { regfree( global_pat ); free( global_pat ); }
- global_pat = tpat;
- patlock = true; /* reserve pattern */
+ if( **ibufpp == '\n' )
+ {
+ if( isglobal && (*ibufpp)[1] != 0 )
+ { set_error_msg( "Invalid newline substitution" ); return false; }
+ 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 ( tpat ? true : false );
+ return true;
}
-/* modify text according to a substitution template; return offset to
- end of modified text */
-static int apply_subst_template( const char * const boln,
+/* 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 )
{
- const char * sub = stbuf;
+ const char * sub = rbuf;
- for( ; sub - stbuf < stlen; ++sub )
+ for( ; sub - rbuf < rlen; ++sub )
{
int n;
if( *sub == '&' )
{
int j = rm[0].rm_so; int k = rm[0].rm_eo;
- if( !resize_buffer( &rbuf, &rbufsz, offset + k - j ) ) return -1;
- while( j < k ) rbuf[offset++] = boln[j++];
+ if( !resize_buffer( txtbufp, txtbufszp, offset + k - j ) ) return -1;
+ while( j < k ) (*txtbufp)[offset++] = txt[j++];
}
else if( *sub == '\\' && *++sub >= '1' && *sub <= '9' &&
( n = *sub - '0' ) <= re_nsub )
{
int j = rm[n].rm_so; int k = rm[n].rm_eo;
- if( !resize_buffer( &rbuf, &rbufsz, offset + k - j ) ) return -1;
- while( j < k ) rbuf[offset++] = boln[j++];
+ if( !resize_buffer( txtbufp, txtbufszp, offset + k - j ) ) return -1;
+ while( j < k ) (*txtbufp)[offset++] = txt[j++];
}
else
{
- if( !resize_buffer( &rbuf, &rbufsz, offset + 1 ) ) return -1;
- rbuf[offset++] = *sub;
+ if( !resize_buffer( txtbufp, txtbufszp, offset + 1 ) ) return -1;
+ (*txtbufp)[offset++] = *sub;
}
}
- if( !resize_buffer( &rbuf, &rbufsz, offset + 1 ) ) return -1;
- rbuf[offset] = 0;
+ if( !resize_buffer( txtbufp, txtbufszp, offset + 1 ) ) return -1;
+ (*txtbufp)[offset] = 0;
return offset;
}
-/* replace text matched by a pattern according to a substitution
- template; return size of the modified text */
-static int replace_matching_text( const line_t * const lp, const int gflags,
- const int snum )
+/* 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_t * 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( global_pat, txt, se_max, rm, 0 ) )
+ if( !regexec( subst_regex_, txt, se_max, rm, 0 ) )
{
int matchno = 0;
do {
- if( !snum || snum == ++matchno )
+ if( global || snum == ++matchno )
{
changed = true; i = rm[0].rm_so;
- if( !resize_buffer( &rbuf, &rbufsz, offset + i ) ) return -1;
+ if( !resize_buffer( txtbufp, txtbufszp, offset + i ) ) return -1;
if( isbinary() ) newline_to_nul( txt, rm[0].rm_eo );
- memcpy( rbuf + offset, txt, i ); offset += i;
- offset = apply_subst_template( txt, rm, offset, global_pat->re_nsub );
+ memcpy( *txtbufp + offset, txt, i ); offset += i;
+ offset = replace_matched_text( txtbufp, txtbufszp, txt, rm, offset,
+ subst_regex_->re_nsub );
if( offset < 0 ) return -1;
}
else
{
i = rm[0].rm_eo;
- if( !resize_buffer( &rbuf, &rbufsz, offset + i ) ) return -1;
+ if( !resize_buffer( txtbufp, txtbufszp, offset + i ) ) return -1;
if( isbinary() ) newline_to_nul( txt, i );
- memcpy( rbuf + offset, txt, i ); offset += i;
+ memcpy( *txtbufp + offset, txt, i ); offset += i;
}
txt += rm[0].rm_eo;
}
- while( *txt && ( !changed || ( ( gflags & GSG ) && rm[0].rm_eo ) ) &&
- !regexec( global_pat, txt, se_max, rm, REG_NOTBOL ) );
+ while( *txt && ( !changed || ( global && rm[0].rm_eo ) ) &&
+ !regexec( subst_regex_, txt, se_max, rm, REG_NOTBOL ) );
i = eot - txt;
- if( !resize_buffer( &rbuf, &rbufsz, offset + i + 2 ) ) return -1;
- if( i > 0 && !rm[0].rm_eo && ( gflags & GSG ) )
+ if( !resize_buffer( txtbufp, txtbufszp, offset + i + 2 ) ) return -1;
+ if( global && i > 0 && !rm[0].rm_eo )
{ set_error_msg( "Infinite substitution loop" ); return -1; }
if( isbinary() ) newline_to_nul( txt, i );
- memcpy( rbuf + offset, txt, i );
- memcpy( rbuf + offset + i, "\n", 2 );
+ 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 pattern according to
- a substitution template; return false if error */
+/* 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 gflags, const int snum, const bool isglobal )
+ 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;
- set_current_addr( first_addr - 1 );
- for( lc = 0; lc <= second_addr - first_addr; ++lc )
+ for( lc = 0; lc <= second_addr - first_addr; ++lc, ++addr )
{
- const line_t * const lp = search_line_node( inc_current_addr() );
- const int size = replace_matching_text( lp, gflags, snum );
+ const line_t * 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 = rbuf;
- const char * const eot = rbuf + size;
+ const char * txt = txtbuf;
+ const char * const eot = txtbuf + size;
undo_t * up = 0;
disable_interrupts();
- if( !delete_lines( current_addr(), current_addr(), isglobal ) )
+ if( !delete_lines( addr, addr, isglobal ) )
{ enable_interrupts(); return false; }
+ set_current_addr( addr - 1 );
do {
- txt = put_sbuf_line( txt, size, current_addr() );
+ txt = put_sbuf_line( txt, eot - txt );
if( !txt ) { enable_interrupts(); return false; }
if( up ) up->tail = search_line_node( current_addr() );
else
}
while( txt != eot );
enable_interrupts();
+ addr = current_addr();
match_found = true;
}
}
- if( !match_found && !( gflags & GLB ) )
+ if( !match_found && !isglobal )
{ set_error_msg( "No match" ); return false; }
return true;
}
/* signal.c: signal and miscellaneous routines for the ed line editor. */
/* GNU ed - The GNU line editor.
Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
- Copyright (C) 2006-2016 Antonio Diaz Diaz.
+ Copyright (C) 2006-2017 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
*i = 0;
return false;
}
- if( errno == ERANGE || li > INT_MAX || li < INT_MIN )
+ if( errno == ERANGE || li > INT_MAX || li < -INT_MAX )
{
set_error_msg( "Numerical result out of range" );
*i = 0;
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+# 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!!!!!
+.
+a
+to be undone
+.
+u
+w out.o
-aa
+H
+ag
hello world
.
+w out.ro
+++ /dev/null
-aa
-hello world
-.
-hello world
-line 1
+hello world!!!
+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!
-line 2
-line 3
-line 4
-line5
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!!!!!
+++ /dev/null
-0a
-hello world
-.
-2a
-hello world!
-.
-$a
-hello world!!
-.
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
-1ine6
-line7
-line8
-line9
+++ /dev/null
-line 2
-line9
+++ /dev/null
-1 d
-1 1 d
-1,2,d
-1;+ + ,d
-1,2;., + 2d
--- /dev/null
+H
+0p
+w out.ro
--- /dev/null
+H
+2,1p
+w out.ro
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
+++ /dev/null
-line 1
-line 2
-line 3
-okay
-line 4
-line5
+++ /dev/null
-3p
-!read one
-hello, world
-a
-okay
-.
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+0c
+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
-cc
+H
+c0
hello world
.
+w out.ro
+++ /dev/null
-cc
-hello world
-.
at the top
-between top/middle
+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
+++ /dev/null
-0c
-at the top
-.
-4c
-in the middle
-.
-$c
-at the bottom
-.
-2,3c
-between top/middle
-.
#! /bin/sh
# check script for GNU ed - The GNU line editor
-# Copyright (C) 2006-2016 Antonio Diaz Diaz.
+# Copyright (C) 2006-2017 Antonio Diaz Diaz.
#
# This script is free software; you have unlimited permission
# to copy, distribute and modify it.
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"
if [ -d tmp ] ; then rm -rf tmp ; fi
mkdir tmp
+cd "${objdir}"/tmp || framework_failure
-# Generate ed test scripts, with extensions .ed and .red, from
-# .t and .err files, respectively.
-printf "building test scripts for ed-%s...\n" "$2"
-cd "${testdir}"
-
-for i in *.t ; do
- base=`echo "$i" | sed 's/\.t$//'`
- (
- echo H
- echo "r ${testdir}/${base}.d"
- cat "$i"
- echo "w ${base}.o"
- ) > "${objdir}/tmp/${base}.ed"
-done
-
-for i in *.err ; do
- base=`echo "$i" | sed 's/\.err$//'`
- (
- echo H
- echo "r ${testdir}/${base}.err"
- cat "$i"
- echo "w ${base}.ro"
- ) > "${objdir}/tmp/${base}.red"
-done
-
-
-cd "${objdir}"/tmp
+cat "${testdir}"/test.txt > test.txt || framework_failure
+cat "${testdir}"/test.bin > test.bin || framework_failure
+touch zero || framework_failure
fail=0
printf "testing ed-%s...\n" "$2"
-# Run the .ed and .red scripts just generated
-# and compare their output against the .r and .pr files, which contain
-# the correct output.
-
-# Run the *.red scripts first, since these don't generate output;
-# they exit with non-zero status
-for i in *.red ; do
- if "${ED}" -s < "$i" > /dev/null 2>&1 ; then
+# Run the .err scripts first with a regular file connected to standard
+# input, since these don't generate output; they exit with non-zero 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 error scripts again as pipes - these should generate output and
-# exit with error (>0) status.
-for i in *.red ; do
- base=`echo "$i" | sed 's/\.red$//'`
- if cat ${base}.red | "${ED}" -s > /dev/null 2>&1 ; then
+# 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 non-zero
+# 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 ${base}.ro "${testdir}"/${base}.pr ; then
+ 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 remaining scripts; they exit with zero status
-for i in *.ed ; do
- base=`echo "$i" | sed 's/\.ed$//'`
- if "${ED}" -s < ${base}.ed > /dev/null 2>&1 ; then
- if cmp -s ${base}.o "${testdir}"/${base}.r ; then
+# 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}" -s test.txt < "$i" > /dev/null 2> out.log ; 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 zero
+
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}
+++ /dev/null
-hello
-world
-this is a simple
-line of text
-for testing the comment
-command in global lists
+++ /dev/null
-heylo
-woryd
-this is a simpye
-yine of text
-for testing the comment
-command in gyobal lists
+++ /dev/null
-# lines beginning with a `#' should be ignored
-g/./# including in global commands \
-s/l/x/\
-# and in the command list \
-s/x/y/
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+2 d
+. d
+$ d
+-5 d
+u
+u
++3,+4d
+# to be undone
+1,$d
+u
+w out.o
-line 2
+This natural inequality of the two powers of population and of
+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
+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
+++ /dev/null
-1d
-2;+1d
-$d
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+e test.bin
+w out.o
-ee e1.err
+H
+ee test.bin
+w out.ro
+++ /dev/null
-3d
-e e1.ed
-1,2d
+++ /dev/null
-E !echo hello world-
--- /dev/null
+H
+f test.bin
+e
+w out.o
-.e e2.err
+H
+.e test.bin
+w out.ro
+++ /dev/null
-E !echo hello world-
+++ /dev/null
-E !echo hello world-
--- /dev/null
+H
+e test.bin
+1d
+E !cat test.bin
+w out.o
-ee.err
+H
+etest.bin
+w out.ro
+++ /dev/null
-E !echo hello world-
--- /dev/null
+H
+e test.bin
+# modifying the last line of a binary file adds a newline
+$s/x/x/
+w out.o
--- /dev/null
+H
+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
-.f f1.err
+H
+.f test.bin
+w out.ro
-ff1.err
+H
+ftest.bin
+w out.ro
--- /dev/null
+H
+g/./m0
+g//a\
+hello world
+# lines beginning with a `#' should be ignored
+g/hello /# including in global commands \
+s/lo/p!/\
+a\
+order\
+.\
+# and in the command list \
+i\
+caos\
+.\
+-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//caos/d\
+-;/order/;d
+# to be undone
+g/./s//x/g
+u
+w out.o
--- /dev/null
+their 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
+caos
+agrarian regulations in their utmost extent, could remove the pressure
+heLp! world
+of this law which pervades all animated nature. No fancied equality, no
+heLp! world
+comparison of this. I see no way by which man can escape from the weight
+hello world
+caos
+All other arguments are of slight and subordinate consideration in
+hello world
+caos
+me appears insurmountable in the way to the perfectibility of society.
+heLp! world
+constantly keep their effects equal, form the great difficulty 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
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
-g/./s //x/
+H
+g
+w out.ro
+++ /dev/null
-g/./s //x/
+++ /dev/null
-line5
-help! world
-caos
-order
-line 4
-help! world
-caos
-order
-line 3
-help! world
-caos
-order
-line 2
-help! world
-caos
-order
-line 1
-help! world
-caos
-order
+++ /dev/null
-g/./m0
-g/./s/$/\
-hello world
-g/hello /s/lo/p!/\
-a\
-order\
-.\
-i\
-caos
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
-g//s/./x/
+H
+g//d
+w out.ro
+++ /dev/null
-hello world
+++ /dev/null
-g/[2-4]/-1,+1c\
-hello world
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
-g
+H
+g/./s //x/
+w out.ro
+++ /dev/null
-linc 3
-xine 1
-xine 2
-xinc 4
-xinc5
+++ /dev/null
-g/./s//x/\
-3m0
-g/./s/e/c/\
-2,3m1
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+# newlines in replacement are ambiguous; last delimiter can't be omitted
+g/./s/y/y\
+f text.bin
+# the previous s command should not set replacement
+s/./%/
+w out.ro
+++ /dev/null
-hello
-zine 1
-line 2
-line 3
-line 4
-line5
-world
+++ /dev/null
-g/./s/./x/\
-u\
-s/./y/\
-u\
-s/./z/\
-u
-u
-0a
-hello
-.
-$a
-world
-.
+++ /dev/null
-line 1
-line 2
-line 3
+++ /dev/null
-line 1
-line 2
-line 3
-line 2
-line 3
-line 1
-line 3
-line 1
-line 2
+++ /dev/null
-g/./1,3t$\
-1d
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+# 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
+H
ii
hello world
.
+w out.ro
+++ /dev/null
-ii
-hello world
-.
-hello world
-hello world!
-line 1
-line 2
-line 3
-line 4
+
+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!!
-line5
+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.
+++ /dev/null
-0i
-hello world
-.
-2i
-hello world!
-.
-$i
-hello world!!
-.
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+2,3j
+j
+7
+u
+u
+3j
+j
+3,3j
+j
+# to be undone
+1,$j
+u
+w out.o
-line 1
-line 2line 3
-line 4
-line5
+This 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.
+++ /dev/null
-1,1j
-2,3j
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+7l
+2ka
+4kb
+9kc
+13kd
+i
+hello world
+.
+'a,'bd
+'c,'dd
+w out.o
-line 3
+This 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
-line 4
-line5
-line 2
+of this law which pervades all animated nature. No fancied equality, no
+agrarian regulations in their utmost extent, could remove the pressure
+++ /dev/null
-2ka
-1d
-'am$
-1ka
-0a
-hello world
-.
-'ad
-u
-'am0
+H
a
hello
.
.ka
'ad
'ap
+w out.ro
+++ /dev/null
-a
-hello
-.
-.ka
-'ad
-'ap
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
-a
-hello world
-.
-1,$m1
+H
+1,$m5
+w out.ro
+++ /dev/null
-a
-hello world
-.
-1,$m1
-hello world
+++ /dev/null
-line5
-line 1
-line 2
-line 3
-line 4
+++ /dev/null
-1,2m$
-1,2m$
-1,2m$
-$m0
-$m0
-2,3m1
-2,3m3
--- /dev/null
+H
+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
--- /dev/null
+their 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
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
+++ /dev/null
-
-
-hello world
-line 1
-line 2
-line 3
-line 4
-line5
+++ /dev/null
-1
-
-
-0a
-
-
-hello world
-.
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
-hello world
+++ /dev/null
-a
-hello world
-.
-0;/./
--- /dev/null
+H
+ppp
+w out.ro
--- /dev/null
+H
+.P
+w out.ro
--- /dev/null
+H
+w out.o
+a
+hello
+.
+Q
+w out.o
+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.
--- /dev/null
+H
+.q
+w out.ro
--- /dev/null
+H
+.Q
+w out.ro
-w q.o
+H
+w out.ro
a
hello
.
--- /dev/null
+H
+w out.ro
+a
+hello
+.
+# EOF should behave as a 'q' command
--- /dev/null
+H
+w out.ro
+a
+hello
+.
+# EOF in the middle of a command should behave as a 'q' command
+w out.ro
\ No newline at end of file
--- /dev/null
+H
+w out.ro
+a
+# EOF in input mode should behave as a 'q' command
--- /dev/null
+H
+w out.ro
+a
+hello
+# EOF in the middle of a line in input mode should behave as a 'q' command
\ No newline at end of file
--- /dev/null
+H
+w out.ro
+a
+hello
+.
+w !wc
+q
+H
r a-good-book
+w out.ro
+++ /dev/null
-r a-good-book
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+7r test.bin
+u
+u
+.r
+# empty read at current address should not modify the current address
+.r zero
+f test.bin
+-7r
+0r !echo hello world
+1
+r !! %
+# to be undone
+r test.txt
+u
+w out.o
+++ /dev/null
-1;r !echo hello world
-1
-r !echo hello world
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+# appending a binary file does not add a newline
+r test.bin
+w out.o
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+# 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
-r r3.ed
-1;/H/+d
-w r3.o
+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.
+++ /dev/null
-r r3.ed
-1;/H/+d
--- /dev/null
+H
+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&$&$&
+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)/
+# to be undone
+,s/./x/g
+u
+w out.o
--- /dev/null
+^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
+^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.$
--- /dev/null
+H
+s . x
+w out.ro
--- /dev/null
+H
+s/x*/a/g
+w out.ro
--- /dev/null
+H
+s/[xyx/a/
+w out.ro
+H
s/\a\b\c/xyz/
+w out.ro
--- /dev/null
+H
+s//xyz/
+w out.ro
--- /dev/null
+H
+s
+w out.ro
--- /dev/null
+H
+/./
+sr
+w out.ro
-a
-hello
-.
+H
s/[h[=]/x/
+w out.ro
-a
-hello
-.
+H
s/[h[:]/x/
+w out.ro
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
+++ /dev/null
-liene 1
-(liene) (2)
-(liene) (3)
-liene (4)
-(()liene5)
+++ /dev/null
-s/\([^ ][^ ]*\)/(\1)/g
-2s
-/3/s
-/\(4\)/sr
-/\(.\)/srg
-,s/i/&e/
-a
-hello
-.
+H
s/[h[.]/x/
+w out.ro
+++ /dev/null
-a
-hello
-.
-s/[h[.]/x/
-hello
--- /dev/null
+H
+# Missing pattern delimiter must not alter RE nor REPLACEMENT
+s/i/1/g
+s/1/i/
+s/
+s/1
+s//%/g
+w out.ro
--- /dev/null
+H
+s/./&/g3
+w out.ro
--- /dev/null
+H
+s/./&/3g
+w out.ro
--- /dev/null
+H
+s/./&/
+sg3
+w out.ro
--- /dev/null
+H
+s/./&/
+s3g
+w out.ro
--- /dev/null
+H
+s3.3&3p
+w out.ro
--- /dev/null
+H
+sg.g&gp
+w out.ro
--- /dev/null
+H
+sr.r&rp
+w out.ro
--- /dev/null
+H
+sp.p&p
+w out.ro
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
+++ /dev/null
-a
-a
-.
-s/x*/a/g
+++ /dev/null
-a
-a
-.
-s/x*/a/g
-a
+++ /dev/null
-li(n)e 1
-i(n)e 200
-li(n)e 3
-li(n)e 4
-li(n)e500
+++ /dev/null
-,s/./(&)/3
-s/$/00
-2s//%/g
-s/^l
--- /dev/null
+H
+$m7
+,s/xxx/yyy/
+.m$
+w out.ro
--- /dev/null
+H
+w out.ro
+s/a/EOF in the middle of a replacement with newlines \
+should behave as a 'q' command
\ No newline at end of file
+++ /dev/null
-hello world
+++ /dev/null
-a
-hello/[]world
-.
-s/[/]/ /
-s/[[:digit:][]/ /
-s/[]]/ /
+++ /dev/null
-s/\a\b\c/xyz/
+++ /dev/null
-a
-hello world
-.
-/./
-sr
+++ /dev/null
-a
-hello world
-.
-/./
-sr
-hello world
+++ /dev/null
-a
-hello
-.
-s/[h[=]/x/
-hello
+++ /dev/null
-a
-hello
-.
-s/[h[:]/x/
-hello
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+1t0
+2,3t2
+,t$
+u
+u
+t0;/./
+# to be undone
+,t0
+u
+w out.o
-line 1
-line5
-line 1
-line 1
-line 2
-line 2
-line 3
-line 4
-line5
-line 1
-line 1
-line 1
-line 2
-line 2
-line 3
-line 4
-line5
+This 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.
+++ /dev/null
-1t0
-2,3t2
-,t$
-t0;/./
--- /dev/null
+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.
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
+++ /dev/null
-line 1
-hello
-hello world!!
-line 2
-line 3
-line 4
-line5
-hello
-hello world!!
+++ /dev/null
-1;r ascii.o
-u
-a
-hello
-world
-.
-g/./s//x/\
-a\
-hello\
-world
-u
-u
-u
-a
-hello world!
-.
-u
-1,$d
-u
-2,3d
-u
-c
-hello world!!
-.
-u
-u
--1;.,+1j
-u
-u
-u
-.,+1t$
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+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
-line5
+their families.
order
hello world
-line 1
+no anxiety about providing the means of subsistence for themselves and
order
-line 2
+decisive against the possible existence of a society, all the members of
order
-line 3
+of it even for a single century. And it appears, therefore, to be
order
-line 4
+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
+++ /dev/null
-v/[ ]/m0
-v/[ ]/s/$/\
-hello world
-v/hello /s/lo/p!/\
-a\
-order
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line5
--- /dev/null
+H
+5w \!.z
+6W ./!.z
+0r !cat < ./!.z
+r \!.z
+!rm -f ./!.z
+wq out.o
-line 1
-line 2
-line 3
-line 4
-line5
-line 1
-line 2
-line 3
-line 4
-line5
+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
+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.
+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
+++ /dev/null
-w !cat >\!.z
-r \!.z
-w /to/some/far-away/place
+H
+w to/some/far-away/place
+w out.ro
+++ /dev/null
-w /to/some/far-away/place
-ww.o
+H
+wbad_write
+w out.ro
-wqp w.o
+H
+wqp bad_write
+w out.ro
+++ /dev/null
-line 1
-line 2
-line 3
-line 4
-line 5
--- /dev/null
+H
+2,4y
+$x
+3x
+u
+u
+,y
+8y
+$x
+16
+y
+x
+# to be undone
+,y
+x
+u
+w out.o
-2,3y
-$x
-0x
-,y
-$x
-2
-y
-x
-E addr1.ro
+H
x
+w out.ro
-line 2
-line 3
-line 3
-line 1
-line 2
-line 3
-line 4
-line 5
-line 2
-line 3
-line 2
-line 3
-line 1
-line 2
-line 3
-line 4
-line 5
-line 2
-line 3
+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
+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
+++ /dev/null
-2,3y
-$x
-0x
-,y
-$x
-2
-y
-x