5

I try to replace some texts in text files with sed but don't know how to do it with multiple files.
I use:

sed -i -- 's/SOME_TEXT/SOME_TEXT_TO_REPLACE/g /path/to/file/target_text_file

Before i go with the multiple files i printed the paths of targeted text files in a text file with this command:

find /path/to/files/ -name "target_text_file" > /home/user/Desktop/target_files_list.txt

Now i want to run sed according to target_files_list.txt.

3 Answers 3

7

You can loop through the file using while ... do loop:

$ while read i; do printf "Current line: %s\n" "$i"; done < target_files_list.txt

In your case you should replace printf ... with sed command you want.

$ while read i; do sed -i -- 's/SOME_TEXT/SOME_TEXT_TO_REPLACE/g' "$i"; done < target_files_list.txt

However, notice that you can achieve what you want using only find:

$ find /path/to/files/ -name "target_text_file" -exec sed -i -- 's/SOME_TEXT/SOME_TEXT_TO_REPLACE/g' {} \;

You can read more about -exec option by running man find | less '+/-exec ':

   -exec command ;

          Execute command; true if 0 status is returned.  All
          following arguments to find are taken to be arguments to
          the command until an argument consisting of `;' is
          encountered.  The string `{}' is replaced by the current
          file name being processed everywhere it occurs in the
          arguments to the command, not just in arguments where it
          is alone, as in some versions of find.  Both of these
          constructions might need to be escaped (with a `\') or
          quoted to protect them from expansion by the shell.  See
          the EXAMPLES section for examples of the use of the
          -exec option.  The specified command is run once for
          each matched file.  The command is executed in the
          starting directory.  There are unavoidable security
          problems surrounding use of the -exec action; you should
          use the -execdir option instead.

EDIT:

As correctly noted by users terdon and dessert in the comments it's necessary to use -r with read because it will correctly handle backslashes. It's also reported by shellcheck:

$ cat << EOF >> do.sh
#!/usr/bin/env sh
while read i; do printf "$i\n"; done < target_files_list.txt
EOF
$ ~/.cabal/bin/shellcheck do.sh

In do.sh line 2:
while read i; do printf "\n"; done < target_files_list.txt
      ^-- SC2162: read without -r will mangle backslashes.

So it should be:

$ while read -r i; do sed -i -- 's/SOME_TEXT/SOME_TEXT_TO_REPLACE/g' "$i"; done < target_files_list.txt
6
  • 3
    read will not see the last line of the input file if it doesn't end with a newline character, I recommend while IFS='' read -r i || [[ -n "$i" ]]; do … instead.
    – dessert
    Dec 24, 2017 at 11:57
  • 1
    I appreciate your remark. However, note that user uses > that will handle new lines automatically and that it's common in unix world to end files with newilne: stackoverflow.com/questions/729692/…. Dec 24, 2017 at 12:00
  • 1
    You're totally right and that's the way everyone should do that, but I prefer adapting the code to the user, not the other way around. ;)
    – dessert
    Dec 24, 2017 at 12:06
  • @ArkadiuszDrabczyk the problem isn't so much the rare occurrence of a file with no terminal newline, but the much more common issue of file names with spaces. Using while IFS= makes this work on file names with spaces (or tabs, newlines etc) and using -r makes it work for file names with backslashes. Your find solution can deal with arbitrary file names, but the while approach needs the changes dessert suggested to make it robust.
    – terdon
    Dec 24, 2017 at 12:29
  • 1
    @ArkadiuszDrabczyk the only case I'm aware of where it makes a difference (at least in current versions of bash) is if the filename starts with whitespace Dec 24, 2017 at 13:28
3

One way would be to use xargs:

xargs -a target_files_list.txt -d '\n' sed -i -- 's/SOME_TEXT/TEXT_TO_REPLACE/g'

From man xargs:

   -a file, --arg-file=file
          Read items from file instead of standard input.  

   --delimiter=delim, -d delim
          Input  items  are  terminated  by  the specified character.  The
          specified delimiter may be a single character, a C-style charac‐
          ter  escape  such as \n, or an octal or hexadecimal escape code.
2

Just use a for loop.

IFS=$'\n' # Very important! Splits files on newline instead of space.

for file in $(cat files.txt); do
    sed ...
done

Note that you will run into problems if you encounter any files with newlines (!) in their names. (:

You must log in to answer this question.

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