Problem with input from simple script that copies a file to a different directory
As someone trying to learn bash, I have been making simple scripts to test out various commands. In the following simple script, I am experimenting with the read command.
#!/bin/bash
read -p "Enter a path to a file: " file_to_copy
cp $file_to_copy /tmpIf the user enters the entire path to a test file e.g. /home/$USER/test when prompted, the script executes as expected and makes a copy of "test" in the /tmp directory. However, if the user enters the shorthand ~/test, terminal returns the error cp: cannot stat '~/test': No such file or directory. Why is the command cp unable to find an equivalent representation of the file path when entered?
If I independently enter in terminal:
cp ~/test /tmpthe test file is copied without incident, so it is probably a nuance of variable assignment that I messed up, but I cannot figure it out.
Thanks.
2 Answers
Try these commands in your script:
#!/bin/bash
read -p "Enter a path to a file: " file_to_copy
file_to_copy=$(bash -c "echo $file_to_copy")
file_to_copy=${file_to_copy/~\//$HOME\/}
cp "${file_to_copy}" /tmpExplanation:
The third line will expand
envvariable values if any present.The fourth line in the above code will try to search for
~in yourfile_to_copyvariable and if found it will be replace by$HOMEpath.And
\/here\is a backslash escape sequence character for/, as we want to replace~/with$HOME/.
What happens is that you literally ask shell to find ~/test from the directory the script is currently in. In other words, if you run from /home/$USER, it will be looking for file test in subdirectory named ~, not the expansion /home/$USER/test.
What you can use is bash's eval built in, which will take any arguments and put them together as one command and let the shell run it. Now, shell will expand ~.
xieerqi:
$ ./copyScript.sh
Enter a path to a file: ~/test
Entered /home/xieerqi/test
xieerqi:
$ ls -l /tmp/test
-rw-rw-r-- 1 xieerqi xieerqi 0 Dec 21 01:14 /tmp/test
xieerqi:
$ cat copyScript.sh
#!/bin/bash
read -p "Enter a path to a file: " file_to_copy
file_to_copy=$(eval echo $file_to_copy)
echo "Entered $file_to_copy"
cp "$file_to_copy" /tmpI'd suggest you also use quotes around the command-substitution $( . . .) , to ensure that files with spaces in them and special character will be properly captured into file_to_copy variable
ADDITION
As stated in the comments, eval built-in can be dangerous.
Personally, I use the readlink command quite often in my scripts and avoid using ~. And since in this script you ask for user input anyway, why not use command line arguments ? Like so:
xieerqi:
$ ./copyScript.sh testFile.txt
/home/xieerqi/testFile.txt
xieerqi:
$ cat copyScript.sh
#!/bin/bash
FILE="$(readlink -f "$@" )"
echo $FILE
# uncomment for copying
# cp $FILE /tmpdirname and basename
GNU coreutils provides two commands, basename and dirname. basename shows last element in the path , and dirname shows everything preceding last element. One could use bash's internal parameter expansion to do the same, but since we have these tools - why not use them:
xieerqi:
$ ./copyScript.sh
Enter path to file: ~/testFile.txt
File entered is /home/xieerqi/testFile.txt
xieerqi:
$ ./copyScript.sh
Enter path to file: ~/sumFile.txt
File entered is /home/xieerqi/sumFile.txt
ERROR, file doesn't exist
xieerqi:
$ cat copyScript.sh
#!/bin/bash
read -p "Enter path to file: " FILEPATH
DIRPATH="$(dirname "$FILEPATH")"
FILENAME="$(basename "$FILEPATH" )"
if [ "$DIRPATH" == "~" ];then DIRPATH="$HOME"
fi
FILEPATH="$DIRPATH"/"$FILENAME"
echo "File entered is " "$FILEPATH"
[ -e "$FILEPATH" ] || { echo "ERROR, file doesn't exist"; exit 1;} 4