4

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 /tmp

If 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 /tmp

the 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

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" /tmp

I'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 /tmp

dirname 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;}
  • Alright. I can understand that. I don't have enough rep to upvote these responses, but I'll come back when I do. Thanks for the explanation, guys. – mike3759 Dec 21 '15 at 8:32
  • 3
    @mike3759 Note that this should be used carefully. Running eval on an arbitrary string this way allows code injection. To make an example, if the script had to run some sudo commands for a non-sudoer user and was setup in /etc/sudoers to not require the sudo password, enetering $(exec sudo -i) would have given root access to the non-sudoer user. – kos Dec 21 '15 at 8:42
  • I'll remember that. I found a program on github that allows me to compile .sh scripts that I write and make them executables. I also found out through some guesswork that placing the executable in /usr/bin makes it runable as a command in terminal. To avoid the problem you brought up, couldn't I just not specify any sudo commands in my script, and make the user input something like "sudo mycommand" into terminal? It works that way with commands such as "apt-get", no? You can't run that command without being a super user. – mike3759 Dec 21 '15 at 8:58
  • 1
    @mike3759 scripts don't need to be compiled, they are executable once you do chmod +x /path/to/script.sh Whatever that program was, I'd suggest you get rid of it. Also, you don't need to necessarily to place them into /usr/bin. You just need to change a few things in $HOME/.profile file and have personal $HOME/bin folder – Sergiy Kolodyazhnyy Dec 21 '15 at 9:00
4

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}" /tmp

Explanation:

  • The third line will expand env variable values if any present.

  • The fourth line in the above code will try to search for ~ in your file_to_copy variable and if found it will be replace by $HOME path.

  • And \/ here \ is a backslash escape sequence character for /, as we want to replace ~/ with $HOME/.

  • That works. Thanks. You explained that it is searching for the ~ symbol in your response, but can you explain the commands that do that? I don't understand the series of back and forward slashes you used in the variable reassignment of line 3. How does that series of slashes translate to searching for a ~? – mike3759 Dec 21 '15 at 8:16
  • @kos bash -c "echo $file_to_copy" this will expand the env variables values from the file_to_copy variable, thanks for correction. – snoop Dec 21 '15 at 9:15
  • 1
    @snoop although this works, this is also has security danger , same as eval in my answer. Using ` sudo -i ` also allows gaining access to root shell. Note the two asterisks – Sergiy Kolodyazhnyy Dec 21 '15 at 9:28
  • 1
    @kos Here:paste.ubuntu.com/14126465 Arbitrary command execution possible with echo and backticks – Sergiy Kolodyazhnyy Dec 21 '15 at 9:36
  • 1
    Hey, guys ! I've played around with dirname and basename a little. Check the edit – Sergiy Kolodyazhnyy Dec 21 '15 at 10:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.