linux:sed

This is an old revision of the document!


SED

sed (von stream editor) ist ein nicht-interaktiver Texteditor für die Verwendung auf der Kommandozeile oder in Skripten. sed zählt zu den “Urgesteinen” in der Unix- / Linux-Welt und ist quasi in jeder Linux-Installation (auch Minimalinstallationen) enthalten.

# sed [Optionen] sed-Skript [Textdatei...]

oder

# sed [Optionen] [-e sed-Skript | -f Skriptdatei] [Textdatei...] 

Das Ergebnis wird auf der Standardausgabe ausgegeben. Wird keine Datei angegeben, so wird die Standardeingabe verwendet. Die Syntax von sed-Skripten findet man in der info-Seite von sed oder über die Links am Ende des Artikels. sed-Skripte verwenden reguläre Ausdrücke ähnlich denen von grep. Es ist empfehlenswert, sed-Skripte durch Hochkommas (') zu schützen, damit die Shell Sonderzeichen nicht selbst auswertet.


Diese Parameterliste ist unvollständig. Weiteres findet sich in der man-page von sed.

Optionen von sed
Kurzform Langform Beschreibung
-n –quiet, –silent Verhindert das automatische Ausgeben des Ergebnisses. Ausgaben erfolgen nur über das Kommando p.
-e Skript –expression=Skript Angeben eines sed-Skriptes, mehrere sed-Skripte sind möglich. Bei nur einem sed-Skript kann -e weggelassen werden.
-f Skriptdatei –file=Skriptdatei Das sed-Skript steht in der Skriptdatei, nicht auf der Kommandozeile.
-i –in-place Die Textdatei wird verändert, anstatt das Ergebnis auf Standardausgabe auszugeben.
  • Jedes Auftreten von “Werner” wird durch “Urs” ersetzt (aber auch “Wernerius” wird zu “Ursius”). Wird g (global) weggelassen, wird nur das erste Auftreten in einer Zeile ersetzt.
    # sed s/Werner/Urs/g Textdatei
  • Alle Wörter “Werner” werden durch “Urs” ersetzt (nicht “Wernerius”), aber nur in Zeilen, die “Name” enthalten.
    # sed /Name/s/\bWerner\b/Urs/g Textdatei
  • Ersetzt alle “Werner” durch “Urs” und gibt nur die betroffenen Zeilen aus.
    # sed -n s/Werner/Urs/gp Textdatei

Zeilen die mit # anfangen, werden entfernt.

# sed '/^#/d' Textdatei
  • Vor der dritten Zeile wird “Neue Zeile” eingefügt.
    # sed '3iNeue Zeile' Textdatei
  • Hier wird eine “Neue Zeile” nach der vierten Zeile eingefügt.
    # sed '4aNeue Zeile' Textdatei
  • Hier wird eine “Neue Zeile” nach der letzten Zeile eingefügt.
    # sed '$aNeue Zeile' Textdatei

Alle Zeilen, die mit “E-Mail:” anfangen, werden ersetzt.

# sed 's/^E-Mail:.*$/E-Mail-Adresse ist privat/' Textdatei

Normalerweise wird “/” als Trennzeichen verwendet. Es lässt sich aber beliebig austauschen, was beim Bearbeiten von Dateinamen nützlich ist.

# sed 's!/home/werner/!/home/urs/!' Textdatei

# echo "This is just a test" | sed -e 's/ /_/g'

This_is_just_a_test


As I was replacing tokens with secrets in an init container for a Kubernetes Pod, I was not getting it to work correctly. Not until I actually took a look at the resulting configuration file, I was getting some hints as to what was wrong. I had used sed before when replacing tokens with URI’s, but apparently not URI’s with multiple query-string parameters which are joined by ampersands (&).

Let’s replace the token %link% with a URI

$ URI='https://example.com/?num=7'
$ echo 'my_link = %link%' | sed "s|%link%|${URI}|"
my_link = https://example.com/?num=7

Seems good. Let’s add another parameter to the URI

$ URI='https://example.com/?num=42&type=magic'
$ echo 'my_link = %link%' | sed "s|%link%|${URI}|"
my_link = https://example.com/?num=42%link%type=magic

Surprise. What I expected the link to look like was https://example.com/?num=42&type=magic

A quick search revealed the culprit;

The REPLACEMENT can contain \N (N being a number from 1 to 9, inclusive) references, which refer to the portion of the match which is contained between the Nth \( and its matching \). Also, the REPLACEMENT can contain unescaped & characters which reference the whole matched portion of the pattern space

What I could do here is to first sanitise the URL by backslashing all occurences of & before using it as the replacement

URI='https://example.com/?num=42&type=magic'
$ echo 'my_link = %link%' | sed "s|%link%|$(echo "$URI" | sed 's|&|\\&|g')|"
my_link = https://example.com/?num=42&type=magic

But to me, that seems clunky. I would rather switch to a different replacer to avoid corner cases like this and excessive code in the init containers. Trying with a perl script instead

URI='https://example.com/?num=42&type=magic'
$ echo 'my_link = %link%' | perl -p -e "s|%link%|${URI}|"
my_link = https://example.com/?num=42&type=magic

This seems to be okay, but let’s add login credentials and see what happens

URI='https://user:pass@example.com/?num=42&type=magic'
$ echo 'my_link = %link%' | perl -p -e "s|%link%|${URI}|"
my_link = https://user:pass.com/?num=42&type=magic

So this time around perl uses @ for named capture groups. In which case I first have to escape them like \@. So I’m where I started out again.

Let’s try with awk

URI='https://user:pass@example.com/?num=42&type=magic'
$ echo 'my_link = %link%' | awk "{ gsub(/%link%/, \"$URI\"); print }"
my_link = https://user:pass@example.com/?num=42%link%type=magic

Same problem as with sed in that it replaces & with the matching string.

I yield. I found a nice script written by Ed Morton which sanitises both the search and the replacement string before running sed one last time. I added an option to do in-place file replacements.

#!/bin/sh
# Modified version of Ed Morton's sedstr:
# https://stackoverflow.com/a/29626460/90674
# stigok, 2019

# In-place option
opts=''
if [ "$1" = '-i' ]; then
  opts='-i ' # note trailing space
  shift
fi

old="$1"
new="$2"
file="${3:--}"
escOld=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< "$old")
escNew=$(sed 's/[&/\]/\\&/g' <<< "$new")
sed ${opts}"s/$escOld/$escNew/g" "$file"

This is finally a working solution for me with all the possible special characters of the URI in question, without breaking or triggering unwanted features and variable expansions in sed and bash.

  • linux/sed.1557153462.txt.gz
  • Last modified: 2019/05/06 16:37
  • by michael