0

I'm trying to make a script which asks for a directory path and then creates that directory. I'd like to be able to pass variables to the read builtin so pathnames so I don't have to type out full paths:

function make_dir {
echo "What is the path of the directory to be created?"
read directory
mkdir "$directory"
}

so I'd type:

make_dir
What is the path of the directory to be created?
$HOME/temp_dir
mkdir: cannot create directory `$HOME/temp_dir': No such file or directory

So I'd like to have $HOME expanded into /home/user/ and the script to make the directory /home/user/temp_dir, but I can't seem to get the expansion to work.

If I modify the make_dir function to show_dir below

function show_dir {
echo "What is the path of the directory to be created?"
read directory
echo "The directory is $directory"
}

and I type $HOME/temp_dir and get the following:

make_dir
What is the path of the directory to be created?
$HOME/temp_dir
The directory is $HOME/temp_dir

with no expansion. Any ideas on how to get this to work?

  • 3
    The only answer is `eval` which is not a good solution. With `read -e` tab completion might expand it for you and any readline bindings that expand variables (like `meta-ctrl-e` or `esc ctrl-e`) will expand it before you hit enter. – Etan Reisner Nov 26 '14 at 18:56
  • Thanks for the reply, adding `eval` in front of `mkdir "$directoy"` seems to work, can you help me understand why this is not a good solution? – Brian Albert Monroe Nov 26 '14 at 19:10
  • 1
    there is a good explanation here http://stackoverflow.com/questions/17529220/why-should-eval-be-avoided-in-bash-and-what-should-i-use-instead – midori Nov 26 '14 at 19:13
  • Also http://mywiki.wooledge.org/BashFAQ/048 – Etan Reisner Nov 26 '14 at 19:16
  • @BrianAlbertMonroe apart from using eval as Etan mentioned, you'd use mkdir -p as well. OR mkdir won't work if a folder doesn't already exist for any of the sub path folders. – AKS Nov 26 '14 at 19:31
  • Great stuff, always learning. Can't I get around this issue by putting `newdir=$(eval echo "$directory")` after the `read directory` line and chage `mkdir "$directory"` to `mkdir "$newdir"`? If some evil command was passed into `read` wouldn't `echo` just pass the entered command as text into the variable `newdir` without executing it? – Brian Albert Monroe Nov 26 '14 at 19:37
  • 1
    @BrianAlbertMonroe The problem is if `$directory` expands to something like `some string; rm -rf /*`. `eval` then gets the string `echo some string; rm -rf /*` to execute. – chepner Nov 26 '14 at 20:03

3 Answers3

1

It's a little cumbersome, but one option is to use the -e flag to tell read to use Readline to get the input, then use Readline to expand the line after typing it, but before hitting Enter.

$ read -e directory
$HOME/dir

Now type Meta-Control-e, and Readline will expand the input just as if it were being processed prior to execution as a shell command. (Note that the Meta key is probably Alt or Esc, depending on your terminal setup.)

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Better than having an `eval` disaster I suppose, but as you say it's cumbersome and it won't expand `~` into `/home/user`. – Brian Albert Monroe Nov 26 '14 at 20:25
  • It should expand `~`. – chepner Nov 26 '14 at 20:31
  • There is a program out there that takes a string and outputs the string that results from substitution the value of any *environment* variables in the input, but for the life of me I can't remember what it is called. – chepner Nov 26 '14 at 20:32
  • Strange, I tried again and it worked. Must've hit a wrong key. That program would be perfect, should you remember please post it. Until then I think I'm just going to type full paths for this particular function. – Brian Albert Monroe Nov 26 '14 at 21:01
1

You are actually making things more difficult by attempting to get the directory with read. Unless you have an absolute requirement to use read, you are better off passing the directory to your function as an argument. For example:

function make_dir {
    [ -n "$1" ] || {
        printf "\n usage: make_dir <path_to_create>\n\n"
        return 1
    }
    mkdir -p "$1" || {
        printf "\n error: unable to create '$1', check permissions\n\n"
    }
}

example:

$ function make_dir {
>     [ -n "$1" ] || {
>         printf "\n usage: make_dir <path_to_create>\n\n"
>         return 1
>     }
>     mkdir -p "$1" || {
>         printf "\n error: unable to create '$1', check permissions\n\n"
>     }
> }

$ make_dir $HOME/temp_dir

$ ls -al temp_dir
total 8
drwxr-xr-x  2 david david 4096 Nov 26 15:34 .
drwxr-xr-x 76 david david 4096 Nov 26 15:34 ..

$ make_dir

usage: make_dir <path_to_create>

When you pass the directory to your function as an argument instead of using read, you can easily adjust your function to take/create multiple directories as well:

function make_dir {
    [ -n "$1" ] || {
        printf "\n usage: make_dir <path_to_create> [path2, ..]\n\n"
        return 1
    }
    for i in "$@" ; do
        mkdir -p "$i" || {
            printf "\n error: unable to create '$i', check permissions\n\n"
        }
    done
}

example:

$ make_dir temp_dir_{1..3}
$ ls -1d temp_*
temp_dir_1
temp_dir_2
temp_dir_3
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

change the following line:

mkdir "$directory"

with

eval mkdir -p "$directory"
AKS
  • 16,482
  • 43
  • 166
  • 258
  • 1
    Very dangerous; this allows a malicious user to craft a "directory name" that contains an arbitrary command line, and it will execute. Verification with `[[ -d $directory ]] && eval mkdir -p "$directory"` is a little better, but I would still recommend against using `eval` here. – chepner Nov 26 '14 at 20:00