Skip to content

-i edits in place

Level: 201 · before you point a one-liner at a file you care about

One line: -i replaces each input file with whatever the loop prints — with -p that is every line, with -n only the lines you print, and with print STDOUT nothing at all, which leaves the file empty; -i.bak keeps the original, and it behaves the same on macOS and Linux, which sed -i does not.

Measured

The script works on a scratch copy of demo/orders.txt, so the lesson folder is never edited:

Verified output of in_place_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ perl -i.bak -pe 's/Warsaw/Warszawa/' orders.txt

$ ls
orders.txt
orders.txt.bak

$ cat orders.txt
1001 alice Kraków 12.50
1002 bob Warszawa 7.25
1003 alice Gdańsk 30.00
1004 carol Warszawa 4.75

$ cat orders.txt.bak
1001 alice Kraków 12.50
1002 bob Warsaw 7.25
1003 alice Gdańsk 30.00
1004 carol Warsaw 4.75

$ perl -MO=Deparse -i.bak -pe 's/Warsaw/Warszawa/' 2>/dev/null
BEGIN { $^I = ".bak"; }
LINE: while (defined($_ = readline ARGV)) {
    s/Warsaw/Warszawa/;
}
continue {
    die "-p destination: $!\n" unless print $_;
}

$ perl -i -ne 'print unless /bob/' orders.txt

$ ls
orders.txt
orders.txt.bak

$ cat orders.txt
1001 alice Kraków 12.50
1003 alice Gdańsk 30.00
1004 carol Warszawa 4.75

$ mkdir originals && perl -i'originals/*' -pe 's/alice/Alice/' orders.txt

$ ls originals
orders.txt

$ cat orders.txt
1001 Alice Kraków 12.50
1003 Alice Gdańsk 30.00
1004 carol Warszawa 4.75

$ perl -i -ne 'print STDOUT "read: $_"' orders.txt
read: 1001 Alice Kraków 12.50
read: 1003 Alice Gdańsk 30.00
read: 1004 carol Warszawa 4.75

$ test -s orders.txt || echo 'orders.txt is now empty'
orders.txt is now empty

What -i changes

Deparse shows that -i.bak adds one line, BEGIN { $^I = ".bak"; }, and nothing to the loop. $^I is what changes how ARGV behaves: while it is set, each file named on the command line is read as usual, and the loop's output — every print that does not name a filehandle — becomes that file's new contents.

Read the four runs with that in mind:

  1. -i.bak -pe-p prints every line, so the file is rewritten with the substitution applied, and orders.txt.bak holds the original.
  2. -i -ne 'print unless /bob/'-n prints only what you print, so bob's line is deleted. With no extension after -i, no backup is made: ls still shows only the first run's .bak.
  3. -i'originals/*' — a * in the extension stands for the file name, so the backup is originals/orders.txt. The directory has to exist first.
  4. -i -ne 'print STDOUT ...' — every line went to the terminal and none to the default output, so orders.txt is now empty. No warning; it did what it was told.

Before you press Enter

Run it with -i.bak first, compare the backup with the result, and delete the backup once you trust it. A one-liner that forgets -p, or prints to a named handle, empties every file it was given — all of them, before you see the first.

sed -i is two different options

GNU sed takes -i with an optional suffix attached; BSD sed, on a Mac, needs an argument even when it is empty, sed -i ''. A script written on one fails on the other. perl -i -pe is the same command on both, and CI runs this lesson on both. The encodings library measures the sed side: sed matches patterns, not bytes ↗.

If you are coming from another language

  • Python. The standard library's fileinput module has an in-place mode — fileinput.input(files, inplace=True, backup='.bak') — in which print writes into the file being read. Same idea, same way to empty a file.
  • The shell. cmd < file > file truncates file before cmd reads it. -i exists so you never write that.

See also