1

I have created the following code. At the end I want to process each file that is in the array individually but somewhere in this code I have not included the double quotes so the file names with a space in the middle it treats as 2 entries in the array:

#!/bin/bash

EXT=(sh mkv txt)

EXT_OPTS=()
# Now build the find command from the array
for i in "${EXT[@]}"; do
    EXT_OPTS+=( -o -iname "*.$i" )
done

# remove the first thing in EXT_OPTS
EXT_OPTS=( "${EXT_OPTS[@]:1}" )

# Modify to add things to ignore:
EXT_OPTS=( '(' "${EXT_OPTS[@]}" ')' ! '(' -iname "*test*" -o -iname "*sample*" ')' )

#echo "${EXT_OPTS[@]}"

searchResults=($(find . -type f "${EXT_OPTS[@]}"))

#echo "$searchResults"

for R in "${searchResults[@]}"; do
    echo "$R"
    sleep 1
done

So the results I am getting are:

./Find2.sh
./untitled
2.sh
./countFiles.sh
./unrar.sh
./untitled
3.sh
./untitled
4.sh
./clearRAM.sh
./bash_test.sh
./Test_Log.txt
./untitled.txt
./Find.txt
./findTestscript.sh
./untitled.sh
./unrarTest.sh
./Test.sh
./Find.sh
./Test_Log
copy.txt
./untitled
5.sh
./IF2.sh

As an example untitled 5.sh has been added to the array as 2 entries. where have I forgot to add the "s

cheers

EDIT with suggested edits:

#!/bin/bash

EXT=(sh mkv txt)

EXT_OPTS=()
# Now build the find command from the array
for i in "${EXT[@]}"; do
    EXT_OPTS+=( -o -iname "*.$i" )
done

# remove the first thing in EXT_OPTS
EXT_OPTS=( "${EXT_OPTS[@]:1}" )

# Modify to add things to ignore:
#EXT_OPTS=( "${EXT_OPTS[@]:-1}" )
EXT_OPTS=( '(' "${EXT_OPTS[@]}" ')' ! '(' -iname "*x0r*" -o -iname "*torrent*" ')' )

#echo "${EXT_OPTS[@]}"

#searchResults=($(find . -type f "${EXT_OPTS[@]}"))

#echo "$searchResults"

#for R in "${searchResults[@]}"; do
#   echo "$R"
#   sleep 1
#done


find . -type f "${EXT_OPTS[@]}" -exec sh -c '
    for pathname do
        printf "%s\n" "$pathname"
        sleep 1
    done' sh {} +

Now Produces:

./Find2.sh
./untitled 2.sh
./countFiles.sh
./unrar.sh
./untitled 3.sh
./Find3.sh
./untitled 4.sh
./clearRAM.sh
./bash_test.sh
./Test_Log.txt
./untitled.txt
./Find.txt
./findTestscript.sh
./untitled.sh
./unrarTest.sh
./Test.sh
./Find.sh
./Test_Log copy.txt
./untitled 5.sh
./IF2.sh
./Find4.sh
Andy M
  • 119

2 Answers2

2

No, you should not parse the output of find. If you want to do something with the pathnames that find finds, you should do that from within find:

find ...stuff... -exec sh -c '
    for pathname do
        printf "%s\n" "$pathname"
        sleep 1
    done' sh {} +

Here, find will call the in-line shell script with batches of found pathnames (the script may be called more than once). find acts as a sort of pathname generator for the internal script.

Another approach is to use -print0 to output the pathnames with \0 in-between them, and then read them with a tool that knows how to read the nul-terminated pathnames. The nul-character \0 is the only character that is not allowed to be part of a Unix pathname, so that's the only safe way to pass them to another tool.

Related:

Kusalananda
  • 333,661
  • I see.....

    I have not been writing bash for very long so i'm still learning.

    what I was trying to do was set up the next bit to deal with the various file types. For example if it was a .sh or a .txt files to be copied to one folder and additional to that all the .tmp files go to another folder and then .log files to another folder.

    What function do I need to read about to do that ? I was going to use a case function but is that the best way to do it?

    – Andy M Feb 08 '19 at 22:53
  • @madmiddle To simplify it, I would make one find for each case. find ... -type f -name '*.tmp' -exec mv -t tmp-files {} + for .tmp files, for example. – Kusalananda Feb 08 '19 at 22:58
  • @madmiddle Though you could do it in one huge find command with \( -name '*.tmp' -exec mv ... \) -o \( ... \) -o \( ... \) etc., but it wouldbe ugly and hard to maintain. – Kusalananda Feb 08 '19 at 23:00
  • thats why I started with the array at the beginning and if i needed to add a new file type then all i needed to do was add to the array at the top and then again at a case statement at the bottom. But like i said I'm still very new to this. – Andy M Feb 08 '19 at 23:03
  • 1
    @madmiddle The issue is fully described and explained in the first question I linked to in my answer. The shell will split all found pathnames on spaces, tabs and newlines (by default) and your loop will loop over these split-up words. – Kusalananda Feb 08 '19 at 23:04
  • I have now edited the post to incorporate your suggestions. Also thank you for the links full of useful info. – Andy M Feb 09 '19 at 00:05
1

With newer versions of bash (> 4.4 IIRC), the mapfile builtin allows you to specify a null delimter - you can then use find ... print0 as suggested in Kusalananda's answer e.g.

mapfile -t -d '' searchResults < <(find . -type f "${EXT_OPTS[@]}" -print0)

If that's not an option, then a slower uglier way would be something like

while IFS= read -r -d '' f; do 
  searchResults+=("$f")
done < <(find . -type f "${EXT_OPTS[@]}" -print0)

See the related bash: whitespace-safe procedural use of find into select

steeldriver
  • 81,074