manderson
manderson

Reputation: 15

Copy files based on content

Need to find all files in specific subdirectories ending in .xml. They'll either be in .../etc/apps/<dirname>/local/data/ui/views/*.xml or .../etc/apps/<dirname>/default/data/ui/views/*.xml. If the file in /etc/apps/<dirname>/default/data/ui/views/*.xml doesn't have the string version="1.1" in the first line, copy the file to the local directory as the same file name, add that string to the first line and quit.

I'm stuck on copying the file from its default dir to its local dir. I've got:

src=.../etc/apps/*/default/data/ui/views/*.xml
dest=.../etc/apps/*/local/data/ui/views/
pat='version='
for file in $src
do
    if [[ "$(sed -n '1{/version=/p};q' "$file")" ]]; then
        awk 'NR==1 {print; exit}' "$file"
        echo "No change necessary to $file"
    else
        echo "Copying $file to local ../local/data/ui/views/$file and running sed '1 s/>/ version="1.1">/'"
    fi
done

Which won't work because it's going to use the whole path for $file like I told it to. I get the feeling I'm overthinking this. But I'm not sure where to go from here.

Upvotes: 0

Views: 116

Answers (4)

Ivan
Ivan

Reputation: 7277

Use grep like so:

grep -vril 'version="1.1"' .../etc/apps/*/default/data/ui/views/*.xml

It'll give you the list of files you without version="1.1" Something like that may work:

while read file_path; do
    file_name=$(basename "$file_path")
    echo 'version="1.1"' > ./"$file_name"
    cat  "$file_path"   >> ./"$file_name"
done < <(grep -vril 'version="1.1"' .../etc/apps/*/default/data/ui/views/*.xml)

Upvotes: 0

Fravadona
Fravadona

Reputation: 16960

A few remarks:

  • Instead of copy/modify the file, generating a new one in the local directory will be more efficient.

  • awk can test the first line, print it, and even modify it, all at the same time.

Here's an idea of what you could do:

edit: handling the glob in the path and the possible pre-existence of the destination file

#!/bin/bash
shopt -s nullglob

for default_dir in .../etc/apps/*/default/data/ui/views/
do
    local_dir=${default_dir/\/default\///local/} # replaces the first occurrence of "/default/" with "/local/"

    for src_file in "$default_dir"*.xml
    do
        dest_file=${local_dir}${src_file##*/}

        # we'll process the local file when it's present (instead of the default one)
        [[ -e "$dest_file" ]] && src_file=$dest_file

        if line=$( awk '
            {
                ok = /version=/
                if (!ok)
                    sub( />/, "version=\"1.1\">" )
                print
                exit !ok
            }
        ' "$src_file"
        )
        then
            printf 'No change necessary to %q\n' "$src_file"
        else
            printf 'Generating %q' "$dest_file"

            { echo "$line"; cat "$src_file"; } > "$dest_file".tmp &&
            mv "$dest_file".tmp "$dest_file"
        fi
    done
done

Upvotes: 1

Jetchisel
Jetchisel

Reputation: 7791

Here is one solution/approach with bash and find and ed to edit the file in place, but change to something available in your system.

#!/usr/bin/env bash

str='version='
value=1.1
src=../etc/apps/default/data/ui/views
dest=../etc/apps/local/data/ui/views

while IFS= read -rd '' -u3 files; do
  IFS= read -r first_line < "$files"
  if [[ $first_line = *"$str"* ]]; then
    printf 'No change necessary to %s\n' "$files"
  else
    printf "Copying %s to local %s\n" "$files" "$src/${files##*/}"        
    cp -v "$files" "$dest" || exit
    printf 'Inserting %s at the first line of %s\n' "$str$value" "$dest/${files##*/}"
    printf '%s\n' '0a' "<?xml $str${value}?>" . w q | ed -s "$dest/${files##*/}"
  fi
done 3< <(find "$src" -type f -name '*.xml' -print0)

Create a dummy directories and files.

mkdir -vp ./etc/apps/default/data/ui/views/
mkdir -vp ./etc/apps/local/data/ui/views/
touch  ./etc/apps/default/data/ui/views/{foo,bar,baz,more}.xml
echo 'version="1.1"' > ./etc/apps/default/data/ui/views/foo.xml

Check the created directories and files.

tree ./etc

Output

./etc/
└── apps
    ├── default
    │   └── data
    │       └── ui
    │           └── views
    │               ├── bar.xml
    │               ├── baz.xml
    │               ├── foo.xml
    │               └── more.xml
    └── local
        └── data
            └── ui
                └── views

9 directories, 4 files

Check the contents of the files in the default directory.

tail -n+1 ./etc/apps/default/data/ui/views/*.xml 

Output

==> ./etc/apps/default/data/ui/views/bar.xml <==

==> ./etc/apps/default/data/ui/views/baz.xml <==

==> ./etc/apps/default/data/ui/views/foo.xml <==
version="1.1"

==> ./etc/apps/default/data/ui/views/more.xml <==

Now execute your script but remove the first . from the path, e.g.

src=./etc/apps/default/data/ui/views
dest=./etc/apps/local/data/ui/views

Output

Copying ./etc/apps/default/data/ui/views/bar.xml to local ./etc/apps/local/data/ui/views/bar.xml
'./etc/apps/default/data/ui/views/bar.xml' -> './etc/apps/local/data/ui/views/bar.xml'
Inserting version="1.1" at the first line of ./etc/apps/local/data/ui/views/bar.xml
Copying ./etc/apps/default/data/ui/views/more.xml to local ./etc/apps/local/data/ui/views/more.xml
'./etc/apps/default/data/ui/views/more.xml' -> './etc/apps/local/data/ui/views/more.xml'
Inserting version="1.1" at the first line of ./etc/apps/local/data/ui/views/more.xml
Copying ./etc/apps/default/data/ui/views/baz.xml to local ./etc/apps/local/data/ui/views/baz.xml
'./etc/apps/default/data/ui/views/baz.xml' -> './etc/apps/local/data/ui/views/baz.xml'
Inserting version="1.1" at the first line of ./etc/apps/local/data/ui/views/baz.xml
No change necessary to ./etc/apps/default/data/ui/views/foo.xml

Check the contents of directories.

tree ./etc

Output

./etc/
└── apps
    ├── default
    │   └── data
    │       └── ui
    │           └── views
    │               ├── bar.xml
    │               ├── baz.xml
    │               ├── foo.xml
    │               └── more.xml
    └── local
        └── data
            └── ui
                └── views
                    ├── bar.xml
                    ├── baz.xml
                    └── more.xml

9 directories, 7 files

Check the file contents from the local directory.

tail -n+1 ./etc/apps/local/data/ui/views/*.xml

Output

==> ./etc/apps/local/data/ui/views/bar.xml <==
<?xml version="1.1"?>

==> ./etc/apps/local/data/ui/views/baz.xml <==
<?xml version="1.1"?>

==> ./etc/apps/local/data/ui/views/more.xml <==
<?xml version="1.1"?>

The script works as intended, just make an adjustment to your needs like Changing the ed code from

printf '%s\n' '0a' "<?xml ${str}?>" . w q | ed -s

to your sed or prefererably an xml editor/parser that can be scripted like xmlstarlet and the likes.


As per the OP's comment including the * glob in the path and an existing file to dest should not be overwritten. The * does not expand on a variable assignment. An array is needed for that.

#!/usr/bin/env bash

shopt -s nullglob

top_dirs=(./etc/apps/*/)
dest=local/data/ui/views/
src=default/data/ui/views/

shopt -u nullglob

str='version='
value=1.1    

while IFS= read -rd '' -u3 files; do
  IFS= read -r first_line < "$files"
  ##: skip files if it is in local/data/ui/views/, don't overwrite.
    [[ -e "${files/default/local}" ]] && continue
  if [[ $first_line = *"$str"* ]]; then
    printf 'No change necessary to %s\n' "$files"
  elif [[ $first_line != *"$str"* ]]; then
    printf "Copying %s to local %s\n" "$files" "${files/default/local}"
    cp -v "$files" "${files/default/local}"
    printf 'Inserting %s at the first line of %s\n' "$str$value" "${files/default/local}"
    printf '%s\n' '0a' "<?xml $str${value}?>" . w q | ed -s "${files/default/local}"
  fi
done 3< <(find "${top_dirs[@]/%/"$src"}" -type f -name '*xml' -print0)

The above script was tested against the dummy files/directories with:

mkdir -vp ./etc/apps/dir{1..5}/default/data/ui/views/
mkdir -vp ./etc/apps/dir{1..5}/local/data/ui/views/
touch ./etc/apps/dir{1..5}/default/data/ui/views/{foo,bar,baz,more}.xml
echo 'version=' > ./etc/apps/dir1/default/data/ui/views/foo.xml

Upvotes: 0

Necklondon
Necklondon

Reputation: 998

You might want to use pipes and basename. E.g.

ls <DIR>/*.xml | while read FULLPATH; do 
   SRC=$(basename $FULLPATH)
   <your code>
done

Upvotes: 0

Related Questions