1

I am frequently copy-pasting files from a Windows share to a local folder. For this, I would like to have a small bash script where I can search for a file name, have a menu based on this search to select from and then finally copy the selected file to my local folder. As searching the local share every time again would cost me a lot of time (and the files don't change frequently), I have a list of all files in filelist.txt.

Through copy-pasting from the many help sites on the select menu I came up with the following code. However, there is a problem with the grep output and the select menu. Somehow, newline is not recognized by this and as an option #1 I have a list of all filenames and option #2 is quit. That means, the script doesn't split the grep output into different options...

How can I avoid this behaviour? What am I doing wrong? Can I use grep in this way or do I need to use a different function?

Script example_script:

#!/bin/bash
read -p 'What are you searching for?  ' file_search_name
prompt="Please select a file:"
options=$(grep $file_search_name $HOME/Local/Folder/filelist.txt)

PS3="$prompt "
select opt in "${options[@]}" "Quit" ; do 
  if (( REPLY == 1 + ${#options[@]} )) ; then
    exit

  elif (( REPLY > 0 && REPLY <= ${#options[@]} )) ; then
    echo  "You picked $opt which is file $REPLY"
    break

  else
    echo "Invalid option. Try another one."
  fi
done

cp ~/Windows/Share/"$opt" $HOME/Local/Folder/"$opt"

sample output:

$ example_script
What are you searching for?  File
1) File1.txt
File2.txt
File3.txt
...
2) Quit
Please select a file:
4

You need to store the results of grep as an array, not a string:

options=( $(grep $file_search_name $HOME/Local/Folder/filelist.txt) )

But that won't work well if your file names have spaces. So, use the mapfile builtin instead (see help mapfile for details), and you should also quote your variables. Here's a working version of your script:

#!/bin/bash
read -p 'What are you searching for?  ' file_search_name
prompt="Please select a file:"
mapfile -t options < <(grep "$file_search_name" "$HOME/Local/Folder/filelist.txt") 

PS3="$prompt "
select opt in "${options[@]}" "Quit" ; do 
  if (( REPLY == 1 + "${#options[@]}" )) ; then
    exit

  elif (( REPLY > 0 && REPLY <= "${#options[@]}" )) ; then
    echo  "You picked $opt which is file $REPLY"
    break

  else
    echo "Invalid option. Try another one."
  fi
done

To understand what is happening, try the following example:

$ cat file
foo
bar
baz
a line with spaces

Say you now grep for a:

$ grep a file
bar
baz
a line with spaces

If you save that as you would in your original script:

options=$( grep a file)

This is a single string. So, ${options[@]} expands to that single string:

$ for i in "${options[@]}"; do echo "Option: $i"; done
Option: bar
baz
a line with spaces

Now, try again but save as an array instead:

$ options=( $( grep a file) )
$ for i in "${options[@]}"; do echo "Option: $i"; done
Option: bar
Option: baz
Option: a
Option: line
Option: with
Option: spaces

That's better, at least it's an array, not a string, but as you can see it didn't handle the spaces well. This is where mapfile comes in:

$ mapfile -t options < <(grep "$file_search_name" "$HOME/Local/Folder/filelist.txt")
$ for i in "${options[@]}"; do echo "Option: $i"; done
Option: bar
Option: baz
Option: a line with spaces

The -t tells mapfile not to include the trailing newline character (\n) of each input line in the value read into the array.

6
  • Great, that helps a lot! Especially with the explanation I finally get why you would put extra brackets. Thanks :) However, now I have another problem: all files with a blank space are split into several options, e.g. file with white space.txt would yield options 1) file 2) with 3) white 4) space.txt. Any idea what is wrong here? I made sure that I quoted all variables... (I'm used to write in R and everything seems much more confusing in the shell) – bamphe Apr 27 '20 at 18:20
  • @bamphe heh, R must be the one language whose syntax is even more unpleasantly complicated than the shell's! Anyway, see updated answer. It should work with everything except file names with newlines now. If you need to handle such file names, you will have to change your input file so it is NULL-separated, but that should be a different question. – terdon Apr 27 '20 at 18:40
  • Nice that you replied that quick. Unfortunately I don't really get the meaning of the mapfile, did you forget the filtering in the last step? I tried to put the grep somewhere but this always breaks the code. Now options is just the mapfile of file, right? – bamphe Apr 27 '20 at 18:53
  • Actually, I realised that options=( $( grep a file) ) already works for the short example. But the problem lies somewhere in the actual script... – bamphe Apr 27 '20 at 19:07
  • @bamphe I'm an idiot! I added the mapfile and completely forgot the grep! Sorry about that. Try the updated answer. I'm afraid I'm on mobile so I haven't tested it, but it should work. I'll update with an explanation when I'm back at a computer. – terdon Apr 27 '20 at 21:37

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.