5

I have a list of files in one directory and a set of jpegs corresponding to each file in another directory. I need to loop over all files, and for each file name, determine the target directory.

For example, if I have three text files called foo.txt, bar.txt and baz.txt in /home/userA/folder/, the corresponding jpegs will be in /home/userA/folder2/foo/, /home/userA/folder2/bar/ and /home/userA/folder2/baz/.

I wrote a script that should loop over all txt files and get the corresponding target directories but it's giving me an error:

bash: /home/userA/folder/File1.txt: syntax error: operand expected (error token is "/home/userA/folder/File1.txt")`

My script:

#!/bin/bash
FILES=/home/userA/folder/*.txt
for i in $FILES
do
    str1=$i | cut -d'/' -f5 #to get the name of the file
    echo /home/userA/folder2/$i_filename #I want to include the .txt filename in the output to be like this /home/userA/folder2/Text1_filename    
done

How can I fix this?

2

Using find:

#!/bin/bash
path="/home/userA/folder"
find "$path" -maxdepth 1 -type f -name "*.txt" -print0 | while read -d $'\0' file; do
    a="$path/$(basename $file)/a_%06.jpg"
    echo "$a
done
3

If all you want is to get the file's name and use it to get the right target directory, you can do:

#!/bin/bash
for i in /home/userA/folder/*.txt
do
    ## Get the file name
    str1="${i##*/}"

    ## Get the target directory
    dir="/home/userA/folder2/${str1%.txt}/"
done

This is using the shell's native string manipulation features. ${var##pattern} removes the longest match of pattern from the start of $var and ${var%pattern} removes the shortest match of pattern from the end of $var. So, ${i##*/} removes everything until the last / (the path) from the file name and ${i%.txt} removes the string .txt from the end of it.

  • But using this still gives the error in my question. I dont know why :/ – Tak Jun 24 '15 at 14:26
2

You just forget the echo and backquotes and field number on this line

    str1=`echo $i | cut -d'/' -f5 `#to get the name of the file

But basename might be a better option.

    str1=`basename $i` #name of the file

Like this

#!/bin/bash
FILES=/home/userA/folder/*.txt
for i in $FILES
do
    str1=`basename "$i"` #to get the name of the file
    echo $str1
    ls -l "`dirname "$i"`/$str1"
done

For a good answer about dealing with for loops and files with spaces in their name refer to This answer

  • Thanks for your answer but this didn't fix the problem. The error still appears – Tak Jun 24 '15 at 11:31
  • yes have updated. – X Tian Jun 24 '15 at 11:33
  • I still get the same error. Actually the script doesn't enter the loop. It stops giving this error in the FILES=/home/userA/folder/*.txt where File1 (which is the first .txt file in the location) is fetched. – Tak Jun 24 '15 at 11:37
  • your answer still gives the same error. Seems there is something wrong with the FILES=/home/userA/folder/*.txt – Tak Jun 24 '15 at 12:06
  • look at/show the output of ls -1 /home/userA/folder/*.txt do some of your files have spaces in them ? Check the link I added at the end of my answer. – X Tian Jun 24 '15 at 12:16
2

Do you mean this?

 ~/test $ for i in F/*.txt; do n="TARGET/$(basename "$i" .txt)"; echo "$n"; done
TARGET/a
TARGET/b
TARGET/c
 ~/test $ ls F
a.txt  b.txt  c.txt
 ~/test $

And with special chars and spaces:

 ~/test $ for i in F/*.txt; do n="TARGET/$(basename "$i" .txt)"; echo "\"$n\""; done
"TARGET/a b€C\""
"TARGET/a"
"TARGET/b"
"TARGET/c"
 ~/test $ ls F
a b€C\".txt  a.txt  b.txt  c.txt
  • Yup. But this is the same as Xtian's answer. – terdon Jun 24 '15 at 14:25
  • The difference is, that basename filters the extension by the second argument basename "$i" .txt so the .txt isn't returned. – ikrabbe Jun 24 '15 at 14:29
  • Oh, true, I missed that. In his defense, that wasn't in the original version of the question. – terdon Jun 24 '15 at 14:33
0

If you really want to put it in a variable, you can use a bash array:

#!/bin/bash
FILES=(/home/userA/folder/*.txt)
for i in "${FILES[@]}" # double qouting pervents extra word splitting
do
    bn="$(basename "$i")" # to get the name of the file
    a="/home/userA/folder2/$bn/a_%06d.jpg"
done

Or you can simply use for i in /home/userA/folder/*.txt.

  • small question though, if there's space in filenames, isn't that array element going to be split into two ? – Sergiy Kolodyazhnyy Jun 24 '15 at 13:31
  • @Serg Luckily, no. Try this snippet: mkdir -p /tmp/t; cd /tmp/t; touch 1 '2 3' ' 4'; printf '%q ' *; echo $'\tdirect * args'; arr=(*); printf '%q ' "${arr[@]}"; echo $'\tafter array expansion'. – Arthur2e5 Jun 24 '15 at 13:33
  • @Serg As some extra information, File/Pathname Expansion is performed after Field Splitting in POSIX Shell Command Language. GNU Bash does the same, although they call these two Word Splitting and Filename Expansion respectively. – Arthur2e5 Jun 24 '15 at 13:37
  • printf: %q: invalid conversion specification direct * args printf: %q: invalid conversion specification after array expansion – Sergiy Kolodyazhnyy Jun 24 '15 at 13:39
  • @Serg We are talking about bash, so I assumed that you tested it in bash where there is %q in the printf builtin which can help you correctly quote the arguments. Since we aren't getting that serious, use f(){ for i; do echo -n "'$i' "; done; }; mkdir -p /tmp/t; cd /tmp/t; touch 1 '2 3' ' 4'; f *; echo $'\tdirect * args'; arr=(*); f "${arr[@]}"; echo $'\tafter array expansion'. – Arthur2e5 Jun 24 '15 at 13:42
0
find /home/userA/folder/ -maxdepth 1 -type f -name "*.txt" -print0 | while read -r -d '' file; do filename=$(printf "%s\n" "$file" | awk -F'/' '{gsub(".txt","");print $5}' ); printf "/home/userA/folder2/%s" "$filename"; done

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.