-2

I have a list of directories (or folders if you will).

In each directory is a file called <something>.txt that is generated from a database dump.

I want to run a cron job that goes through and renames the <something>.txt to "${PWD##*/}"_info.txt, but only if the *.txt file is there, because when that file is missing, the mv command fails and then respecting the cd .. it continually moves up a directory, failing to complete the remaining checks which are deliberately not illustrated or indicated in the code snippet below.

So the code CONTEXT is:

for f in <list of directories>
do
  cd "${f}"
  if [ -e "*.txt" ]; then       ← This is the part that is the question
    mv *.txt "${PWD##*/}".txt   ← The rest is merely to provide context
  fi
  cd ..
done

Any expert thoughts on this little snippet?

I cleaned up the example. The critical part is the conditional:

[ -e "*.txt" ]

Even if a <something>.txt file exists, the conditional fails.

That's what I am asking about.

6
  • 1
    I find your question very confusing, and the code doesn't make sense to me. Perhaps you could clarify with a little example what exactly it is you want to do? Nov 18, 2016 at 21:31
  • Is the filename always info.txt? Nov 18, 2016 at 22:21
  • First sentence, you have a file named ".txt" ?
    – Jeff Schaller
    Nov 19, 2016 at 0:08
  • (1) You say .txt, info.txt and *.txt.  (2) What are you looping over?  (3) You’re missing a done.  (99) Please clarify.  Please do not respond in comments; edit your question to make it clearer and more complete. Nov 19, 2016 at 0:34
  • The biggest problem with the above code is that *.txt could expand to multiple files so your move command risks data loss by moving a bunch of files to a single file. Nov 19, 2016 at 1:34

2 Answers 2

1

On possibility would be to create an array with those items from the glob, then you could see if the array has any elements. For example:

cur_opt=$(shopt -p nullglob) # get current status so we can restore
shopt -s nullglob # to let the glob expand to nothing
txt_files=( *txt )
$cur_opt # restore nullglob setting to what it was before
if [ ${#txt_files} -gt 0 ]; then
    ...
fi

then if you wanted to operate on the files you could iterate over the array as well instead of just testing if the array has any elements

If you really just want to see if there exists anything that would match, you could use compgen like

if [ ! -z "$(compgen -G "*.txt") ]; then
    ...
fi

this could also just check the return for compgen so could be

if compgen -G "*.txt" &>/dev/null; then
    ...
fi
1
  • beautiful. Thank you. I needed to know if I was too far out of the box on that conditional. Plus, I never heard of that tool before....
    – Ken Ingram
    Nov 19, 2016 at 2:09
0

Expert thoughts on this little snippet:

  1. If you’re dealing with a list of directories, and a bunch of files (with names not known in advance), don’t use f to represent a directory name.  That’s just too confusing.
  2. When you’re dealing with a shell variable singly, in isolation (not concatenated with anything else), simply, without modification (e.g., by ##, %, : or /), you don’t need curly braces.  See When should I assign double quotes around variables like "${var}" to prevent problems?

    So, I suggest that the outer loop should look like this:

    for d in <list of directories>
    do
        cd "$d"
        (Code to execute in each directory)
        cd ..
    done

In addition to @Eric Renouf’s perfectly good answer, there are several ways to write the (Code to execute in each directory).  Here’s a simple one, close to what you tried:

for f in *.txt
do
    if [ -e "$f" ]
    then
        mv -- "$f" "${PWD##*/}".txt
    fi
done

If there is a single <something>.txt file in the directory, then this will do what you want and expect: the for f … loop will run once, with $f equal to the name of the file, and the file will be renamed.  (I use mv -- to protect against files whose names begin with - and would otherwise be interpreted as options.  mv ./"$f" … would probably also be safe, as long as you’re sure that $f is a file in the current directory, or is a relative pathname.)  If there are no *.txt files in the directory, then, also, the loop will run once, with $f equal (literally) to *.txt.  Since there is no file by that name, the -e test will fail and the mv will not be executed.

There’s a problem if there are multiple text files in the directory.  If there are n such files, then the loop will run n times, and each text file will be renamed to "${PWD##*/}".txt.  As Zachary Brady pointed out, this can result in the last file overwriting the first n−1 files.  One way to fix this is to add the -i option to the mv command, so you will be warned of any attempt to overwrite an existing file, and given the option to approve it or deny it.  This is, of course, not useful if the script is running as a cron job.  Another approach is to add a break command to the loop:

for f in *.txt
do
    if [ -e "$f" ]
    then
        mv -- "$f" "${PWD##*/}".txt
        break
    fi
done

This will cause the loop to execute exactly once, because, when it does run, it executes the break command, which terminates the loop.  This way, only the first text file will be renamed, and any others will be left alone.  You will not be notified that there were multiple files.

A slightly more sophisticated approach is to detect and handle the multiple file case:

first=1
for f in *.txt
do
    if [ -e "$f" ]
    then
        if [ "$first" ]
        then
            mv -- "$f" "${PWD##*/}".txt
            first=
        else
            printf "Warning: file %s not renamed.\n" "$f"
        fi
    fi
done

in which the first flag is set at the beginning of the loop, and then cleared during the first iteration, so subsequent iterations know that they are not the first.  Alternatively, you could check whether "${PWD##*/}".txt already exists.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .