I would like to rename files with following extensions: .txt, .data, .conf to ".xml"

hello.txt -> hello.xml

To do so, file must also contain the following line: <?xml version="1.0" encoding="UTF-8"?>

This is what I have:

for file in *
if [ $(grep -Rc '<?xml version="1.0" encoding="UTF-8"?>' --include ".txt" --include ".data" --include "*.conf") = true ]
rename extension to: .xml

Any ideas?

  • You used the -R option to grep, so you need recursion, right? I. e., you need to affect matching files in directories below the current one?
    – Quasímodo
    Mar 22, 2020 at 21:38
  • The string <?xml version="1.0" encoding="UTF-8"?> must be in the first line of the file? Mar 22, 2020 at 22:22
  • @Quasímodo Correct.
    – Japkutija
    Mar 22, 2020 at 22:38
  • @PauloTomé Not necessarily.
    – Japkutija
    Mar 22, 2020 at 22:40

4 Answers 4


If you need to do it with grep and for then perhaps something like this?

grep -RlZ '<?xml version="1.0" encoding="UTF-8"?>' --include "*.txt" --include "*.data" --include "*.conf" | 
  xargs -0 sh -c 'for f; do echo mv -- "$f" "${f%.*}.xml"; done' sh

(remove the echo once you are satisfied that it's doing the right thing).

  • grep -RlZ outputs a null-delimited list of the names of files where matches are found

  • xargs -0 passes that null-separated list to sh -c

  • for f loops over the filenames as positional parameters

or (if you are allowed to use while rather than for) you can skip the xargs and the additional shell scriptlet like

grep -RlZ '<?xml version="1.0" encoding="UTF-8"?>' --include "*.txt" --include "*.data" --include "*.conf" | 
  while IFS= read -r -d '' f; do echo mv -- "$f" "${f%.*}.xml"; done
find . -type f \( -name "*.txt" -o -name "*.data" -o -name "*.conf" \) -exec sh -c '
    for file in "$@"; do
        if grep -qF "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "$file"; then
            mv -- "$file" "${file%.*}.xml"
' findshell {} +

I think find is more adequate in this context. It looks recursively for regular files with .txt, .data and .conf extensions and checks if the string you provided is present in each of them. If yes, then it will change the extension to .xml by a mv command.

If you are unsure whether the code will work as expected, you can add an echo before mv to see what it does.

I should also mention that that script does not depend on non-POSIX utilities.


You can try this:

for file in *.{txt,conf}; do 
  [[ $(grep "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "$file") ]] && \
  mv "$file" "${file%.*}.xml" || echo "$file" " does not match"

Using bash:

shopt -s globstar dotglob nullglob extglob

string='<?xml version="1.0" encoding="UTF-8"?>'

for pathname in ./**/*.@(txt|data|conf); do
    if [[ -f $pathname ]] && grep -q -F "$string" "$pathname"; then
        mv -i "$pathname" "${pathname%.*}.xml"

I start by setting a number of shell options that are not usually set in bash by default:

  • globstar enables the ** globbing pattern that matches into subdirectories recursively.
  • dotglob makes globbing patterns match hidden names.
  • nullglob makes a non-matching pattern disappear completely, instead of remaining unexpanded. This ensures that our loop later will not run at all if there is no match.
  • extglob enable extended globbing patterns, for example @(txt|data|conf) which matches one of the strings inside the parenthesis.

We then loop over the candidate names and test each one for the given string. If the string is found, the file is renamed by replacing the filename suffix after the last dot character by xml.


You must log in to answer this question.

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