2

I have a DE_CopyOldToNew.txt file with a whole bunch of copy commands for copying old file names to new file names.

The file contains rows like :

cp /migrationfiles/Company\ Name\ GmbH/2014.138_Old\ File\ Name.pdf /appl/data/docs/202403/DE_2014.138_NewFile_1.pdf
cp /migrationfiles/Company\ Name\ GmbH/2014.139_Old\ File\ Name.pdf /appl/data/docs/202403/DE_2014.139_NewFile_1.pdf

In my shell script I am iterating over each row and execute it.

echo "Start!"
while read -r line
do
  command "$line"
done < /tmp/DE_CopyOldToNew.txt

When executing the script I am getting the following for each row that was read...

 : No such file or directory6: cp /migrationfiles/Company\ Name\ GmbH/2014.138_Old\ File\ Name.pdf /appl/data/docs/202403/DE_2014.138_NewFile_1.pdf

When executing the rows manually from the prompt it works without errors...

2
  • 2
    Can’t you just do bash -c DE_CopyOldToNew.txt?
    – rr0ss0rr
    Mar 4 at 15:41
  • 3
    @rr0ss0rr This command requires DE_CopyOldToNew.txt to be executable and in a directory specified in $PATH, so it probably won't work. bash DE_CopyOldToNew.txt probably will. Mar 5 at 5:07

3 Answers 3

6

The problem is that you are reading the entire line into one shell variable line, which you then use quoted (i.e. as "$line").

While it is usually the right thing to double-quote shell variables when using them, and in particular when dealing (as you do) with filenames that contain spaces or other "problematic" characters, in this case it means that the entire line consisting of the cp command and its arguments is treated as one single shell token in your command statement.

This means that in the loop, you are not executing the cp command with arguments /migrationfiles/Company\ Name\ GmbH/2014.138_Old\ File\ Name.pdf and /appl/data/docs/202403/DE_2014.138_NewFile_1.pdf, but the shell tries to run an executable named

cp /migrationfiles/Company\ Name\ GmbH/2014.138_Old\ File\ Name.pdf /appl/data/docs/202403/DE_2014.138_NewFile_1.pdf

where the spaces that are meant to separate command from arguments are interpreted as being part of the command name. This executable of course doesn't exist.

What you can instead do is read the lines into three variables, as in:

echo "Start!"
while read cmd arg1 arg2
do
  command "$cmd" "$arg1" "$arg2"
done < /tmp/DE_CopyOldToNew.txt

assuming that the structure is always of the form as indicated in your example.

Note that this read command does not contain the -r option, because that would inhibit interpreting the \ as escape character for the following space, which however we need the shell to do here.

0
4

You say,

I have a DE_CopyOldToNew.txt file with a whole bunch of copy commands for copying old file names to new file names.

... and you say you want to execute these cp commands.

You are in luck because you have a shell script, i.e., a file containing a set of commands to be executed in order by a shell. All you need to do is to let a shell execute the commands in the script:

sh DE_CopyOldToNew.txt

This would start sh, a basic shell, and have it execute the file's contents, command by command.

2
  • I guess your 331k reputation is not for nothing... :) That did it! It even told me which file was not copied... Mar 6 at 7:01
  • @RicoStrydom 331k just means I write a lot and have been around for a long time. You still have to evaluate my answers in relation to other peoples' answers, in addition to using your own head. :-)
    – Kusalananda
    Mar 6 at 12:04
2

I wouldn't let the TXT file do any action, but treat it as a source of data, so use the script not only to execute what needs to be executed but also as a control of the data:

# Read each line of the file
while IFS= read -r line; do
    # Get the files using awk with ' /' as separator.
    # $line could have escaped spaces with '\ ' so we use parameter substitution →
    # → to substitute them to unescaped spaces: "${line//\\ / }".
    source_file=$(awk -F' /' '{ print "/"$2 }' <<<"${line//\\ / }") # source file
    destination=$(awk -F' /' '{ print "/"$3 }' <<<"$line") # destination
    
    # For example you can control that the source file exists
    if [[ ! -f "$source_file" ]]; then
        echo "The file $source_file does not exists!"
        exit
    fi
    
    # The cp command is executed here
    cp "$source_file" "$destination"
done </tmp/DE_CopyOldToNew.txt
5
  • It seems as if your solution is added an extra "\" the the existing ones causing a cp: cannot stat ‘/migrationfiles/Company\\ Name\\ GmbH/2014.138_Old\\ File\\ Name.pdf No such file or directory Mar 4 at 16:01
  • @RicoStrydom oh, I will check that and update Mar 4 at 17:16
  • The funny thing is, if I only output the $source_file and $destination then it seems to be the correct path (without the double backslashes) Mar 5 at 6:14
  • @RicoStrydom Edited. this is what I think happens: Before, the value of the variable source_file was holding the literal \ (backslash+space) that the shell uses to escape spaces. In the context of execution of the TXT file that's fine, but in my script the escaping is treated literally, so cp tries to find file names with literal \ instead of spaces. Mar 5 at 12:38
  • @RicoStrydom → if destination had spaces, the substitution should also be applied: destination=$(awk -F' /' '{ print "/"$2 }' <<<"${line//\\ / }"). Mar 5 at 12:43

You must log in to answer this question.

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