LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   (BASH) Works on the command line, hangs as a script -- what's wrong? (https://www.linuxquestions.org/questions/programming-9/bash-works-on-the-command-line-hangs-as-a-script-whats-wrong-820208/)

SilversleevesX 07-16-2010 11:15 AM

(BASH) Works on the command line, hangs as a script -- what's wrong?
 
I'm writing a Bash script to take IPTC keywords from a text file and write them, via Exiv2, to several (first batch is 100) JPEG files in a single directory. The script has one while loop inside another while loop, both terminated, but I'm pretty sure that's not my problem. I think it's how I'm incrementing the "counter" variable, although it could also be the method of parsing the text lines from the file (using cut with delimiters that have worked fine in simpler scripts).

Here's the code as I've worked it up to this point.
Code:

#!/bin/bash
b=1
while read 'line';
do
        n=`echo $line`
        thefile=`echo $n | cut -d: -f1`
        taxt=`echo $n | cut -d: -f2`
        commaz=`echo -e $taxt | grep -o "," | wc -l | sed s/\ //g`
        while [[ $b -le $commaz ]]
        do
                gword=`echo $taxt | cut -d, -f$b`
                exiv2 -M"set Iptc.Application2.Keywords String $gword" $thefile
                echo "Writing keyword $b to $thefile."
                b=$(( $b + 1 ))
        done<testcomment.txt
        echo
done<keywords

And yes, "keywords" checks out in Crimson Editor, Emacs GUI and nano as an ASCII file with UNIX line endings. No issues on that score.

Feeding each line consecutively into a terminal (excepting the exiv2 command) works fine: each variable echoes with the part of the text line used as a variable value as it should, even when the b variable is incremented the quick&dirty way (up arrow three commands and hit enter).

Running the above script in eval mode (sh -x) stalls after setting the b variable to one and reading in the first line of text. I'd like to know why. I'd also like some advice on another reliable method of parsing the read-in lines.

Thanks in advance.

BZT

David the H. 07-16-2010 11:37 AM

Can we see an example of the input files, so we know what it's working with? Also, exactly what output do you get?

There are multiple problems with this script, as well as some other things that I want to comment on.

1.
$(..) is recommended over `..`

2.
Code:

while read 'line';
Why is line enclosed in single-quotes? I don't think it hurts the program, but it doesn't help anything either.

3.
Code:

n=`echo $line`
This is completely unnecessary. First of all, you don't need echo here, as a simple n="$line" would do the trick. But second, there's no reason to use n at all, as $line itself will do just fine in the two commands that follow.

4.
Code:

b=$(( $b + 1 ))
This is ok, but bash will expand variables inside $((..)) automatically, so the extra $ is unneeded. b=$(( b + 1 )) will work.

Edit:

5.
Code:

commaz=`echo -e $taxt | grep -o "," | wc -l | sed s/\ //g`
grep's -o option means to output only the part that matches. So if there's a comma in the text string, you'll only get a comma in the output. Then wc -l counts the number of lines. But since the input is read one line at a time, sed will always only see either 0 or 1, depending on what grep finds, and so has nothing to do. What exactly do you want this line to do?

Edit2:

6.
Code:

while [[ $b -le $commaz ]]; do
....
done<testcomment.txt

The input file here does nothing. And due to problem 5 above, the test will only be true one time at most.

Edit 3

7.
Code:

b=1
Since b is only set to 1 outside the first loop, it will never be reset again after the first iteration. The number will just keep climbing, so the second loop will only run a single time then never again.

SilversleevesX 07-17-2010 01:17 AM

1.Regarding item 1 in your reply:
For what it's worth, all the ticks in the script have now been changed to dollar sign-parentheses pairs. Moving on...

2.Regarding item 5, ditto:
This came from an earlier script that was written to count the commas in the line read by "while read" from the text file. It occurred to me that not every picture in the folders this script would be used with would have the same number of keywords, but that the common element in every set of keywords would be their delimiter. It's already common practice in GUI apps to use a comma, so the earlier script was written to count them and return their number with grep and sed. I confess I disregarded CW with that wc -- I found the line on a forum where the question was "How do you count specific elements in a string of text?"

In this script, I'd still like to be able to count the commas in the nested 'while' loop (or a 'for' or 'case/esac' loop if either of those are faster at the draw), since the fact remains that file gooberfoo.jpg and file RonaldMcDonald.jpg, just to use some off-the-cuff examples, are likely to have a different number of keywords. I thought I had adequately isolated the second part of the string read in from the text file by the time that loop-inside-the-loop (is the proper term here "nested"?) was run with the "taxt=`echo $n | cut -d: -f2` " command. If I keep a while loop here, where should I put the b variable so that it starts at 1 and increments with that loop and that loop quits when b reaches the same number as the number of commas in the part of the string evaluated within the variable taxt? (that's text with an "a", btw)

BZT

Just for the sake of argument, here's a text string of the type that appears in the file keywords:
Code:

guineabissaubeach.jpg:beach,daytime,mid-coast,summer,stay,vacation

grail 07-17-2010 02:05 AM

So my general philosophy here would be KISS (keep it simple silly). I don't know a lot about exiv2 but the below worked for me based on your input:
Code:

#!/bin/bash

for i in $(sed 's/[:,]/ /g' keywords)
do
        if [[ $i =~ .jpg$ ]]
        then
                thefile=$i
        else
                echo exiv2 -M\"set Iptc.Application2.Keywords String $i\" $thefile
        fi
done

Just take out the echo and and the escapes before " and it should be good to go.

SilversleevesX 07-17-2010 06:03 PM

Simple is best -- many thanks.
 
grail,

Thanks a bunch. With one minor tweak ("add" instead of "set" in the exiv2 command), it worked.

"set" in exiv2 is for IPTC, EXIF, and XMP fields and elements that are one-liners (like Date & Time, Caption Writer or Location) "add" is for lists like keywords and supplemental categories, contacts, revision dates, and the like. Hey, like you said, you weren't familiar. No big cheese. :)

Again, thanks. Lucky me -- two issues posted in as many days and solved likewise. 8)

BZT

grail 07-17-2010 10:54 PM

No probs ... glad we could help. Please remember to mark as SOLVED :)

SilversleevesX 07-18-2010 07:23 PM

Just one more niggling little thing. I could almost answer this question myself, but I want to be sure of my ground before I do.

My own practice with IPTC supplemental categories has been to use a list of words that, by comparison with the one for keywords, is only a few shades different. Both are the lists I've generated in, and carried over several version upgrades for use with, iView Media Pro (now MS Expression Media, the first release of version 2 of which I now have and use with these lists). I presume that to write a script to introduce the "supp cats" into these JPEGs, the only changes necessary would be to change the name of the file read in by the sed command and the field written to by the exiv2 -M"add..." one; everything else should work as is.

BZT

grail 07-19-2010 12:28 AM

Sounds plausible, but like all good programming, you should test on a copy first to make sure you are on the right path :)

Good luck.

SilversleevesX 07-20-2010 01:44 PM

Getting ambitious
 
Of course, of course.

I hope it doesn't break any rules or conventions to continue this thread along mainly the same lines as the OP and subsequent posts.

This morning I got ambitious and wrote up a single-line text file that had both Keywords and Supplemental Categories on the same line, following the filename (with path) as I gave in my example (post from the 17th), and delimited by a semicolon instead of a colon from the preceding array of keywords. I introduced another for/do/done loop thusly:
Code:

for j in $(sed 's/[:,]/ /g' listannots)
do
        if [[ $j =~ .jpg$ ]]
        then
                thefile=$j
        else
                exiv2 -M"add Iptc.Application2.SuppCategory String $j" $thefile
        fi
done

..and ran it on the file specified in the text file listannots. When I viewed both keywords and supp cats in a GUI app, the latter had, as its first entry, the full pathname of the file, followed by the other delimiter (the colon); the next five lines were the keywords, and the supplemental cats didn't start until the seventh line. I'm curious as to how I might re-format that for command and its sed to ignore everything until the delimiter that marks off the beginning of the categories list. Or am I better off with a whole separate script with which to enter the supplemental categories?

BZT

grail 07-20-2010 10:29 PM

Sorry ... you will have to dumb it down for me a little.
Try showing what is in listannots so I can follow your logic.
Maybe also show what the following line should look like in your mind based on the input from the file:
Code:

exiv2 -M"add Iptc.Application2.SuppCategory String $j" $thefile

SilversleevesX 07-21-2010 05:23 PM

Quote:

Originally Posted by grail (Post 4040210)
Sorry ... you will have to dumb it down for me a little.
Try showing what is in listannots so I can follow your logic.
Maybe also show what the following line should look like in your mind based on the input from the file:
Code:

exiv2 -M"add Iptc.Application2.SuppCategory String $j" $thefile

I thought, to save a little time, that I might put both keywords and categories in the same text file as the FwP (filename with path), one-two-three, for each file in a folder. I assumed that in order to distinguish between one set of items and another, the sets should be set off by a different delimiter. I also figured that the for-sed combination from the script that worked for writing keywords, would work for supplemental categories if they were set off by, for instance, a colon instead of the semicolon. What I didn't expect, but naturally what I got, was sed reading in both sets, and prefixing the first item with the FwP in the Supplemental Categories 'tab' of my GUI app.

I imagined that
Code:

exiv2 -M"add Iptc.Application2.SuppCategory String $j" $thefile
would write everything in the second set of words, delimited like the first between one another by commas, and ignore the first set. That the for-do-sed-done loop would "jump" the keywords but write just the categories to the file at the appropriate place. I got, as I described above, both more and less than I bargained for.

Here's the kind of line that appears in listannots:
Code:

guineabissaubeach.jpg:beach,daytime,mid-coast,summer,stay,vacation;beach,day,midcoast,midday,summer,Sunday,vacation,warm
Now that you see the second set appended like that, maybe you can help me tweak the for-sed combo to give sed a "pole" with which to "vault over" the keys and read and pass the supp cats to the exiv2 -M"add Iptc.Application2.SuppCategory..." command.

BZT

grail 07-21-2010 07:40 PM

So the issue you have is that sed will make its changes to the whole line and so the for loop looks at the complete line now broken into pieces.
As you have now a need split the initial line and then act on the two separate halves, I came up with the following:
Code:

#!/bin/bash

tmp_arr=($(sed 's/;/ /' listannots))

for i in $(echo ${tmp_arr[0]} | sed 's/[:,]/ /g')
do
        if [[ $i =~ .jpg$ ]]
        then
                thefile=$i
        else
                exiv2 -M"set Iptc.Application2.Keywords String $i" $thefile
        fi
done

for j in $(echo ${tmp_arr[1]} | sed 's/,/ /g')
do
    exiv2 -M"add Iptc.Application2.SuppCategory String $j" $thefile
done

There may be cleaner ways but I will leave you to play :)

SilversleevesX 08-01-2010 02:41 AM

Now the first script won't work.
 
I'm referring to grail's post from the 7th -- or rather the script I made from it which worked when it was new:
Code:

#!/bin/bash

for i in $(sed 's/[:,]/ /g' keywords)
do
        if [[ $i =~ .jpg$ ]]
        then
                thefile=$i
        else
                exiv2 -M"set Iptc.Application2.Keywords String $i\" $thefile
        fi
done

Call me a dunce when it comes to getting my mind around sed, but I can't puzzle out why there's an "else" (do) in that if/fi conditional loop that actually has the command one wants to execute on the files read in from the text list by sed. Unless it's there on the very off-chance that one of the lines in said file does not refer to a JPEG in that directory or anywhere else?

:confused:

BZT
Edit1: It's writing again -- as you see above, I had a very old save with "set" instead of "add" in the Exiv2 command and was using that (egg on face). But I also just now noticed the script isn't strictly delimiting by colons and commas -- in one file in a small set I just ran it on, the single keyword entry "cute ass" got split into "cute" (comma)"ass" and put on two lines: split at the space, I'm presuming. Maybe customizing the $IFS would help with that?

Edit2: Customizing IFS didn't do any good -- neither did setting off each keyword for that file in double-quotes. After writing to that one file, having cleared the original keywords twice, between the sed and Exiv2 "cute ass" became "cute,ass", and it was the one and only keyword (one word or two) surrounded by quotes. I think I need a serious for-ID10Ts explanation of sed. NE1 know where I can get one for cheap? :)

SilversleevesX 08-01-2010 03:54 AM

Getting back to concept
 
Putting it in both broad and narrow terms, and including the rules-of-thumb for formatting I apply to my directory lists, this is what a script like this would have to do with a text file list of files in one directory:

1. Read in the filename. The filename is set off from the keywords by a colon.
Code:

/cygdrive/c/blu/newest/five/amanor-1005-005.jpg:
2. Count the number of keywords in each line. The keywords are one- and two-word substrings delimited by commas.
Code:

adult,cute,cute ass,indoor,naked,redhead
3. Assign a variable to each keyword, one after the other, and pass each to an exiv2 -M"add Iptc.Application2.Keywords" command for the file in the folder whose name is the string as read in from the text file (that part of the whole line that appears before the colon) in succession until the end of the list read in from the text file is reached.
Code:

for x in $(ls $(key) | wc -l); do;done
4. Terminate itself (gently).

Can this be done in BASH? I'm beginning to doubt it.

BZT

SilversleevesX 08-01-2010 06:32 AM

I think I've got it:
Code:

#!/bin/bash
while read 'line';
do
        souse=$(echo $line)
        drunk=${souse%:*}
        barfly=$(basename "$drunk")
        verydrunk=$(echo $souse | cut -d":" -f2)
        booze=${verydrunk//[!,]/}
        bartab=$(echo ${#booze})
        w=1
        while [ $w -le $bartab ]
        do
                key=$(echo $verydrunk | cut -d, -f$w)
                exiv2 -M"add Iptc.Application2.Keywords String $key" $drunk
                w=$[w+1] ;
        done
        echo -e "Keywords added to file $barfly."
done<fivefootone

fivefootone only had a single file referenced and eleven keywords, but it worked. Earlier I tried the list item where the sed script split "cute" and "ass" into two; this one (or rather its one-by-one command line precursors) kept them together. So much for sed and offsetting with established delimiters.

I almost went with a while loop for the whole thing, to keep track of the total number of files worked on (file w of X), and echo that back to the user along with the "Finished this one" kind I did add, but all-of-a-sudden I blanked on how to replace a "while read" command/expression.

BZT


All times are GMT -5. The time now is 04:18 PM.