Last active
October 21, 2018 19:32
-
-
Save rduplain/3441687 to your computer and use it in GitHub Desktop.
Wrap sed in Python to provide ed-style line editing in a simple function call.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"Wrapper to provide ed-style line editing." | |
# Ron DuPlain <[email protected]> | |
from subprocess import Popen, PIPE | |
def edit(text, command): | |
r"""Edit given text using ed-style line editing, using system's sed command. | |
Examples: | |
>>> edit('Hello, world!', 's/world/friends/') | |
u'Hello, friends!' | |
>>> edit('Hello, world!', 'bad_input') | |
Traceback (most recent call last): | |
... | |
EditorException: sed: can't find label for jump to `ad_input' | |
>>> | |
Boring empty cases are handled gracefully: | |
>>> edit('Hello, world!', '') | |
u'Hello, world!' | |
>>> edit('', 's/world/friends/') | |
u'' | |
>>> edit('', '') | |
u'' | |
>>> | |
This is a full ed implementation: | |
>>> edit('Hello hello, world! HELLO!', 's/hello/greetings/gi') | |
u'greetings greetings, world! greetings!' | |
>>> edit('Hello, world!', r's/\(.*\)/You said, "\1"/') | |
u'You said, "Hello, world!"' | |
>>> | |
A note on backslash escaping for sed, remember that Python has it's own | |
escaping. For example, "backslash one" must either be \\1 or provided in a | |
raw string, r'\1'. | |
>>> edit('Hello, world!', 's/\\(.*\\)/\\U\\1/') | |
u'HELLO, WORLD!' | |
>>> edit('Hello, world!', r's/\(.*\)/\U\1/') | |
u'HELLO, WORLD!' | |
>>> | |
""" | |
output = sed_wrapper(text, command) | |
if not output: | |
return u'' | |
return unicode(output) | |
class EditorException(RuntimeError): | |
"An error occured while processing the editor command." | |
def sed_wrapper(text, command): | |
"""Wrap sed to provide full ed-style line-editing. | |
Being the stream editor, open sed in a subprocess and communicate with it | |
using stdio, raising a sensible exception if the command call does not | |
succeed. | |
Note this wrapper deals with shell injection attempts: | |
>>> sed_wrapper('Hello, world!', 's/world/friends/; ls') | |
Traceback (most recent call last): | |
... | |
EditorException: sed: -e expression #1, char 20: extra characters after command | |
>>> sed_wrapper('Hello, world!', '; ls') | |
Traceback (most recent call last): | |
... | |
EditorException: sed: -e expression #1, char 4: extra characters after command | |
>>> | |
""" | |
arguments = ['sed', command] | |
sed = Popen(arguments, stdin=PIPE, stdout=PIPE, stderr=PIPE) | |
sed.stdin.write(text) | |
sed.stdin.close() | |
returncode = sed.wait() | |
if returncode != 0: | |
# Unix integer returncode, where 0 is success. | |
raise EditorException(sed.stderr.read().strip()) | |
return sed.stdout.read() | |
if __name__ == '__main__': | |
# Test with `pyflakes editor.py && python editor.py`. | |
# If you do not have pyflakes, get it! `pip install pyflakes` | |
# See output for errors. No output means that all tests pass. | |
import doctest | |
doctest.testmod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment