Echoing only once for each subdir

I have a script that runs from this:

for i in * ; do (cd $i && echo $i && /test1/execute/testb);done

this is testb:


for file in `ls *.txt`
do
if [ ! "$file" = "$Z" ] && [ ! "$file" = "$Z2" ] && [ ! "$file" = "$Z3" ] && [ ! "$file" = "$Z4" ] &&
[ ! "$file" = "$Z5" ] && [ ! "$file" = "$Z6" ] ; then echo "NO"; break 1;
else
echo "it is there"
fi
done

What is happening is that I can get it to run a command in each subdirectory, but when I do, it prints NO every time it does not find what it is looking for. This is accurate, but I only want it to print the message once for each subdir in which it does not find anything.

I've tried break 1 and break/continue, and it still prints the message many times for each subdirectory. Every time it does not find the file that is specified in the Z1/Z2/Z3 variables, it prints "NO". I only want it once for each directory.

Is there a way to do this?

Why so complicated? Try

ls "$Z" "$Z2" "$Z3" "$Z4" "$Z5" "$Z6" && echo "it is there" || echo "NO"
1 Like

It's good to see that you've tried, but you logic is a little open. Your loop is looking at every file in a directory and then testing to see if the file name matches one of the six you list. You are therefore comparing perhaps thousands of things. You are reading the directory with ls *.txt already so what you could do it something more like:-

for searchfile in $Z $Z2 $Z3 $Z4 $Z5 $Z6
do
   if [ -f $searchfile ]
   then
      echo "$searchfile is in $PWD"
   fi
fi

You can add more if you like, but you are not clear if you actually need all the files to be present or not. Can you clarify if you just need to find any one or all?

Listing multiple files will get an error if any are missing, and you need to handle the output/errors from every list command too.

It might be simpler still to use the find command like this:-

find /start/directory -name $Z -o -name $Z2 -o -name $Z3 -o -name $Z4 -o -name $Z5 -o -name $Z6

Does this give you the output you need in an easier way?

Robin

Rudi:

The solution works great! Out of curiosity, I wonder is there a way to accomplish the same thing with break/continue even though it is not as efficient?

break should work; I didn't analyze your code snippet where and why it would fail.

This is probably what you are looking for:

for i in *
do
   echo $i
   for f in "$Z" "$Z1" "$Z2" "$Z3" "$Z4" "$Z5" "$Z6"
   do
      if [ -f "$i/$f" ]
      then
          echo "it is there"
          continue 2
      fi
    done
    echo "NO"
done

I have expanded this and am trying to get it to make a file that prints "lists are needed" just once under each directory. I have this so far:

DATEN=$(perl -e 'print scalar(localtime(time -  0 * 24 * 60 * 60)), "\n";' |awk '{print $2,$3,$5}')
DATE1=$(perl -e 'print scalar(localtime(time - 1 * 24 * 60 * 60)), "\n";' |awk '{print $2,$3,$5}')
DATE2=$(perl -e 'print scalar(localtime(time - 2 * 24 * 60 * 60)), "\n";' |awk '{print $2,$3,$5}')
DATE3=$(perl -e 'print scalar(localtime(time - 3 * 24 * 60 * 60)), "\n";' |awk '{print $2,$3,$5}')
for i in * ; do (cd $i; echo $i; for f in `ls *.txt`;
do
file=$(truss -vlstat -tlstat ls -l $f 2>&1 |awk 'NR==4'|gawk '{print $3, $4, $7}')
if [ ! "$file" = "$DATEN" ] && [ ! "$file" = "$DATE2" ] && [ ! "$file" = "$DATE3" ]; then
echo "$file" "the file is not correct" >/dev/null
else echo "IT is OK"
fi
done)
done

It will print "the file is not there multiple times if I do not redirect it to /dev/null. I just want it to print once " the file is not there?" I have also tried to tack on an if/then statement to see if I could get it to work, but I can't. I keep getting a message for each file "file is not correct" -- I just want one line with that message per directory. Is there a way to do this without rearranging the entire script?

It is producing multiple "the file is not correct" messages because that's what you have asked it to do.

Your inner loop ask it to consider every file with for f in `ls *.txt` This could be shortened to just for f in *.txt for a starter, but if you have 30 .txt files, you will get up to 30 the file is not correct messages displayed for that directory.

You then have a bit of an open loop with for i in * which will then drive the for f in loop again and again and again. What happens with any files in the current directory (i.e. not directories in themselves)? I expect that they will error on the cd and then carry on with the for f loop in the current directory.

What is the actual requirement of this part of your code? Maybe I'm missing the point, but you are collecting current date and then the same time for the previous three days and then compare this value to the file names that end .txt in a format I don't understand. Can you explain:-

if [ ! "$file" = "$DATEN" ] &&  .........

Is this an "If the value $file is not like the value of $DATEN" or something?

Apologies if I've not learned this syntax yet and it is correct.

Robin

Yes. I am trying to compare file to DATEN. I want to get only one printout that says "file not there" and not a printout of "file not there" for each file in each directory. So I want one message per directory.
I'll check out your suggestions as well -- thanks.

If you are trying to check if two variables match, the syntax would be like this:-

if [ "$file" != "$DATEN" ] .........

Robin

That works as well, but I still get multiple messages for each file that does not match the criteria. How do I get one "the file is not there"? I want it to say that the file is not there once for each directory no matter how many files do not match the criteria. I've tried everything and can not figure it out.

Could you change you code so that you keep the outer loop through the list of directories (considering my earlier comment about what happens when for i in * finds a file that it can't cd to)

Inside each directory, you could simply test if the files exist:-

if [ -f "$DATEN" ] && [ -f "$DATE2" ] && ........

.... which takes out the inner loop and will probably run faster too.

Does this help?

Robin

This is how it looks since yesterday (sorry I did not update it earlier). I believe that the -f DATEN and -f DATE2 does not work because I need to compare what truss outputs to the DATEN and DATE2

All I want to do is have it print "file not found: for each directory in which it does not find any matching date. So far it will print that message when any file in any directory does not match the date, so I will get 10 -15 prints in each directory of the message

DATEN=$(perl -e 'print scalar(localtime(time -  0 * 24 * 60 * 60)), "\n";' |awk '{print $2,$3,$5}')
DATE2=$(perl -e 'print scalar(localtime(time - 2 * 24 * 60 * 60)), "\n";' |awk '{print $2,$3,$5}')


]for i in * ; do (cd $i; echo $i; for f in `ls *.txt`;
do
file=$(truss -vlstat -tlstat ls -l $f 2>&1 |awk 'NR==4'|gawk '{print $3, $4, $7}')
if [ -f "$DATEN" ] && [ -f "$DATE2" ];
then echo "$file" >/dev/null
else echo "YES"
fi
done)
done[

---------- Post updated at 09:58 AM ---------- Previous update was at 09:47 AM ----------

Also, it would help if I could search on "does the file match the date". would this be

 [  "$file" = "$DATEN" ] || [  "$file" = "$DATE2" ]

would that mean if the file matchies date 1 or if it matches date2?

---------- Post updated at 10:17 AM ---------- Previous update was at 09:58 AM ----------

Is there a way to echo a message just once and send the rest to /dev/null ?
So that there would be just one message printed for each directory?

---------- Post updated at 11:03 AM ---------- Previous update was at 10:17 AM ----------

also the previous post you made appears to work somewhat:

for i in * do    echo $i    for f in "$Z" "$Z1" "$Z2" "$Z3" "$Z4" "$Z5" "$Z6"    do       if [ -f "$i/$f" ]       then           echo "it is there"           continue 2       fi     done     echo "NO" done

but it echos yes for everything even though there are no matching files, this
is because I am trying to compare what is in truss command not "do the files exist or not"

Yes, you can print one message per directory. But, only if your code only prints one message per directory. Your code prints one message per file.

[  "$file" = "$DATEN" ] || [  "$file" = "$DATE2" ]

exits with a zero exit status if the string to which $file expands is identical to the string to which $DATEN expands OR the string to which $file expands is identical to the string to which $DATE2 expands.

Please take a few steps backwards here. We understand that you want to print one message per directory indicating whether or not some condition is met by the files in that directory. What is the condition? Please explain in English what you are trying to determine!

Are you looking for a file whose name is the same as the date on which that file was last modified? (It seems EXTREMELY unlikely that the date on which a file was modified is going to end with the four characters .txt !)

For those of us who don't have truss on our system, what is the output from the command:

truss -vlstat -tlstat ls -l $f 2>&1

when f is set to the name of a text file in the current directory?

I'm lost what you are trying to achieve. On the files you are searching for, are you just after matching them on a date? I've very confused. :confused:

Your DATEN, DATE2 etc. values will be the string for the date in the format Mmm dd YYYY where dd is padded with a leading space if required. This matches the output from ls -l when a file is older than 6 months, but a truss gives me no output.

Perhaps we should start again with the logic.

Are you looking for files that match a date-stamp? There are easier ways with the find command.

How many files must you find in a particular directory to regard it as a success? I'm guessing that it could be zero, 1, 2, 3 or 4.

Is there a reason that you compare every file that matches *.txt one at a time? It reads the directory for every file you match. it would be quicker to read the directory for the files you want or read it once and scan the output. What is your matching criteria?

:confused: I'm so confused :confused:

Robin

The output of truss command is as follows:

DATEN=$(perl -e 'print scalar(localtime(time -  0 * 24 * 60 * 60)), "\n";' |awk '{print $2,$3,$5}')

Jun 11 2014 -this is the output of truss command

This date is supposed to be matched against files in each directory:

Dir1
file1--checks truss date against file date
file2
file3

Dir2
filea
fileb
filec

What happens is that it checks every file and so it prints out a message for every file. What I want it to do is print one message for each directory. The message should say "Not found" for any directory that does not have at least one file that matches the date in truss command.

But it prints a message for each file that does not match, because it is not written correctly.

I really don't want to use the find command, this is an experiment with truss and loops for myself .

So is the date part of the file name or the last modified time?

If it's part of the file name try variations on:-

if [ -f "*${DATEN}*" ] || [ -f "*${DATE2}*" ] || [ -f "*${DATE3}*" ] || [ -f "*${DATE4}*" ]
then
    echo "Directory $PWD has at least one file name matching the dates."
else
   echo "Directory $PWD does not contain a file name matching the dates requested."
fi

The logic will be true if a file name containing the first or second or third or fourth date exists in the current directory.

If you are looking for a file modified on the correct date, then that's tricker, however you could try variations on:-

find . -type f -mtime -4 | xargs echo "Directory $PWD contains the following:-"

You may need to add a -prune clause if there are subdirectories you don't want to search down.

Using commands like ls -l to show the directory of files including their last modification date/time is problematic because the date format for files modified within the last 6 months is Mmm dd HH:Mi

If you don't want to use find then I'd have to ask why. It's probably the right tool for the job. Never screw something in with a hammer. If you want to go a bit Heath-Robinson then I'm out.

Of course, if I've missed the point then let me know.

What is the point of the truss command anyway? I'm most confused over that I think.

Am I getting anywhere yet?

Robin

No! It is not! That is the output of a perl command; not the truss command that I asked for:

truss -vlstat -tlstat ls -l $f 2>&1

when f is set to the name of a text file in the current directory? PLEASE SHOW US THE OUTPUT FROM THE ABOVE COMMAND!

From the commands following truss in your pipeline we know that the output from truss has to be at least four lines.

And you still have not told us what you are trying to do! WHAT CHECK ARE YOUR TRYING TO PERFORM BETWEEN FILES IN A DIRECTORY AND YOUR FOUR DATES?

  1. Are you trying to check whether the name of a file in a directory is one of your date strings? (This is what your current code is doing! And, we know it is doomed to fail: a date string ending in "2014" can never be a filename ending in ".txt"!)
  2. Are you trying to check whether or not one of the date strings is part of the name of a file in a directory?
  3. Are you trying to check whether or not the date on which the file was last read matches one of the date strings?
  4. Are you trying to check whether or not the date on which the file was last modified matches one of the date strings?
  5. Are you trying to check whether or not the date on which the file status was last modified matches one of the date strings?
  6. Are you simply trying to determine whether or not any file in a directory has been modified since midnight four days ago?
  7. Are you simply trying to determine whether or not any file in a directory has been modified in the last 96 hours (24 hours/day * 4 days)?

If you can't clearly explain what you are trying to do in unambiguous terms, we can't help you.

2 Likes

Thanks all, for your suggestions. I finally did make this work. I still used truss, and needed some awk as well, but I got it to do what I wanted by carefully inspecting each line of the script.

If you will show us the output you get from running truss and show us your current working script, there is a good chance we can help you make it more efficient.

It will also help other people reading this thread in the future figure out how you solved your problem.