1

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 *
do
if [ $(grep -Rc '<?xml version="1.0" encoding="UTF-8"?>' --include ".txt" --include ".data" --include "*.conf") = true ]
then
rename extension to: .xml
fi
done

Any ideas?

4 Answers4

2
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"
        fi
    done
' 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.

Quasímodo
  • 18,865
  • 4
  • 36
  • 73
2

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
steeldriver
  • 81,074
1

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"
done
1

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"
    fi
done

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.

Kusalananda
  • 333,661
  • @ArkadiuszDrabczyk [[ ... ]] is not a command, like [ ... ] is. It's shell grammar. The expansion therefore does not need to be quoted. See https://unix.stackexchange.com/questions/32210/why-does-parameter-expansion-with-spaces-without-quotes-work-inside-double-brack?noredirect=1&lq=1 – Kusalananda Mar 22 '20 at 22:16