I have a simple problem I want to solve with a bash script: copy a file, and also copy all the files that are imported in that file, and imported in that file, and so on. This screams recursion.
The files look like this:
import "/path/to/otherfile.txt"
import "/path/to/anotherfile.txt"
information
otherinformation
...
Shouldn't be so hard, here's what I wrote:
#!/bin/bash
destination=/tmp
copy_imports () {
insfile=$1
contained_imports=$(grep "import" $insfile | awk -F' ' '{ print $2 }' | sed 's/"//g')
for imported_insfile in $contained_imports
do
copy_imports $imported_insfile
done
cp $insfile $destination
}
copy_imports $1
But for some reason, not all files are copied. I see that it is calling the recursion for all the files and nested imports, but not for all of them the cp
statement is executed.
I'm totally puzzled, what's going on here?
Thanks a lot!
CodePudding user response:
It looks like your script is trying to recursively copy all the imported files and their imports as well. However, there seems to be an issue with the cp statement not being executed for all the imported files.
One possible reason for this could be that some of the imported files might not be present in the current directory, or they might not have the correct permissions to be copied. To check if the imported files are present in the current directory, you can use the ls command to list all the files in the current directory and see if the imported files are present.
Another issue could be that the contained_imports variable might be empty, which would cause the for loop to not execute, and the cp statement would not be executed for the imported file. You can add a check to see if the variable is empty before the for loop to make sure that the cp statement is always executed.
#!/bin/bash
destination=/tmp
copy_imports () {
insfile=$1
contained_imports=$(grep "import" $insfile | awk -F' ' '{ print $2 }' | sed 's/"//g')
if [ -z "$contained_imports" ]; then
cp $insfile $destination
return
fi
for imported_insfile in $contained_imports
do
copy_imports $imported_insfile
done
cp $insfile $destination
}
copy_imports $1
It is also a good practice to add a check if the file exists before trying to copy it using the -f or -e option of the [ command or test command.
if [ -e "$insfile" ]; then
cp $insfile $destination
fi
It's also worth noting that if the imported files have relative path, the script will not work as expected as the current working directory might change during the recursion. In this case, it might be a good idea to use absolute path for the imported files.
CodePudding user response:
The primary problem is that all of the function's variables need to be made local
; without that, all invocations of the function are sharing the same global variables, leading to confusion.
Also, I'd recommend checking whether each imported file has already been copied, to avoid duplicating work. I moved the cp
command before the recursive loop, which effectively flags the file as "done" as soon as processing begins on it (rather than waiting until it's been fully processed).
As tripleee pointed out, double-quoting variable references is almost always a good idea ($contained_imports
is an exception here, since you're counting on word-splitting to break it into separate filenames; if there was a possibility of filenames with spaces, you'd need to use a more robust method). Finally, I couldn't resist replacing the grep | awk | sed
pattern with pure awk
.
I haven't tested this, but I think it'll work:
#!/bin/bash
destination=/tmp
copy_imports () {
local insfile="$1"
if [[ ! -e "$destination/$(basename "$insfile")" ]]; then
cp "$insfile" "$destination"
local contained_imports="$(awk -F' ' '/import/ { gsub("\"", "", $2); print $2 }' "$insfile")"
local imported_insfile
for imported_insfile in $contained_imports
do
copy_imports "$imported_insfile"
done
fi
}
copy_imports "$1"
CodePudding user response:
An other approach would be to get the full list of imported files with awk
and pass it to xargs
for the actual copying:
destdir=/tmp
mkdir -p "$destdir" || exit 1
awk '
BEGIN {
for (i = 1; i < ARGC; i )
if ( !(ARGV[i] in imports) ) {
imports[ARGV[i]]
while ( (getline line < ARGV[i]) > 0 )
if ( line ~ /^import / && match(line, /".*"/) )
ARGV[ARGC ] = substr(line, RSTART 1, RLENGTH-2)
}
for (file in imports)
printf "%s%c", file, 0
}
' "$@" |
xargs -0 sh -c '[ "$#" -gt 0 ] && cp "$@" "$0"/' "$destdir"
note: This script copies all the files, including the ones provided in argument.