44

Is there a way to make sed ask me for confirmation before each replace? Something similar to 'c' when using replace inside vim.

Does sed do this at all?

4
  • 3
    It would be technically possible but more of an intellectual exercise than a useful endeavor. See How to do a text replacement in a big folder hierarchy? which has Vim and Perl solutions. Sep 6, 2011 at 0:20
  • I am using vim (args and argdo) whenever I need 'confirmation', but was wondering if there was a 'simpler' way
    – Yuvi
    Sep 7, 2011 at 19:26
  • 2
    It goes against the basic purpose of sed - to automate editing over a stream.
    – teppic
    Oct 1, 2015 at 17:24
  • @teppic not really because you would still have to go find the instances in text files, which sed can do for you. I think the question makes sense, just in case you added the wrong files to a list and wanted to see what file you were editing Nov 14, 2016 at 22:44

5 Answers 5

48

Doing it with sed would probably not be possible as it's a non-interactive stream editor. Wrapping sed in a script would require far too much thinking. It is easier to just do it with vim:

vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' file.in

Since it was mentioned in comments below, here's how this would be used on multiple files matching a particular filename globbing pattern in the current directory:

for fname in file*.txt; do
    vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' "$fname"
done

Or, if you first want to make sure that the file really contains a line that matches the given pattern first, before performing the substitution,

for fname in file*.txt; do
    grep -q 'PATTERN' "$fname" &&
    vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' "$fname"
done

The above two shell loops modified into find commands that do the same things but for all files with a particular name somewhere in or under some top-dir directory,

find top-dir -type f -name 'file*.txt' \
    -exec vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' {} \;
find top-dir -type f -name 'file*.txt' \
    -exec grep -q 'PATTERN' {} \; \
    -exec vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' {} \;

Or, using the original shell loops and having find feed pathnames into them:

find top-dir -type f -name 'file*.txt' -exec sh -c '
    for pathname do
        vim -c "%s/PATTERN/REPLACEMENT/gc" -c "wq" "$pathname"
    done' sh {} +
find top-dir -type f -name 'file*.txt' -exec sh -c '
    for pathname do
        grep -q "PATTERN" "$pathname" &&
        vim -c "%s/PATTERN/REPLACEMENT/gc" -c "wq" "$pathname"
    done' sh {} +

You do, in any case, not want to do something like for filename in $( grep -rl ... ) since

  1. it would require that grep finishes running before even starting the first iteration of loop, which is inelegant, and
  2. the pathnames returned by grep would be split into words on whitespaces, and these words would undergo filename globbing (this disqualifies pathnames that contains spaces and special characters).

Related:

5
  • 2
    The advantage to sed is it can operate on multiple files, e.g., sed -i 's/old/new/g' /path/to/*.txt or something similar. May 10, 2016 at 12:44
  • 11
    for i in $(grep -rl "old"); do vim -c "%s/old/new/gc" -c "wq" "$i"; done
    – tcpaiva
    Aug 14, 2016 at 2:19
  • @tecepe, please convert it to an answer, Thank You.
    – Nishant
    Dec 30, 2018 at 6:24
  • 1
    @Nishant It would be a fragile answer as it would disqualify any file containing whitespace in its name or path.
    – Kusalananda
    Dec 30, 2018 at 7:32
  • If you don't want to open and close vim a whole bunch, do the entire operation in vim with vim $(grep -RIlZ 'pattern') then in vim use :bufdo %s/pattern/replace/gce | update
    – JKirchartz
    Apr 21, 2020 at 15:16
8

You can get this by doing such:

:%s/OLD_TEXT/NEW_TEXT/gc

Specifically, adding the c after the third delimiter.

Note that the 'c' option only works in Vim; you won't be able to use it with sed at the command line.

2
  • 4
    To clarify: this only works in Vim, not regular command line sed.
    – Brendan
    Oct 1, 2015 at 15:28
  • The OP is tagged with vim, so this answer is applicable; though, clearly vim and sed have different abilities
    – ILMostro_7
    Mar 13, 2016 at 0:35
4

You could let sed do its thing on the file and then save the result to a temporary file which you can then interactively patch into the original file using sdiff (see http://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff):

sed -r 's/something/something_else/g' my_file > tmp_file
sdiff -o my_file -s -d my_file tmp_file
1

If you don't want to open and close vim a whole bunch as other answers suggest, you can do the entire operation in vim...

1: open all files containing your pattern:

vim $(grep -RIlZ 'pattern')

2) in vim replace your pattern in all open buffers (with confirmation):

:bufdo %s/pattern/replace/gce | update

These can even be combined like so

vim $(grep -RIlZ 'pattern') -c ':bufdo %s/pattern/replace/gce | update'
-1
searchandreplace () {
if [ -z $2 ] ; then
    realpath * | xargs grep -ins --directories=skip --color=always "$1" | nl -s "."
else
    realpath * | xargs grep -ins --directories=skip --color=always "$1" | nl -s "."
    exp=$(realpath * | xargs grep -ins --directories=skip --color=never "$1" | cut -d':' -f1,2 | awk -F':' '{print $2 $1}' | sed -E "s|^[0-9]+|sed -i \'&s/|; s|//|/"$1"/"$2"/g\' /|")
    IFS=$'\n'
    for d in $exp
        do
        read -p "Exec $(echo -e -n "\033[1;31m$d\033[0m")? " -e REP
            if [ "$REP" = y ]; then
            echo `bash -c $d`;
            fi
    done
fi
}

If you feed this with single argument –searchandreplace "AAA"– it only searches for that string in the current directory, but if you feed it with double arguments –searchandreplace AAA BBB– then in addition to the above it also asks you to replace the first with the second "line by line" for every line.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .