-1

I'm writing a short bash script to find if a file exists in a directory or not. Here is the script:

#!/bin/bash

> oldFiles.txt

files=$(grep ' jane ' ../data/list.txt | cut -d ' ' -f 3)

for f in $files; do

f=~$f

if [ -e $f ]; then

  echo $f Found

  echo $f>>oldFiles.txt

else
  echo $f Not found

fi

done

Three relevant file names are found by the grep and searched over in the for loop. It appears, however, that every time my script runs the if command, it comes back False regardless of whether the file exists or not. Please see the results of the script, followed by test -e for each of the individual files, followed by a list of the files in the relevant directory. Also posted below is the referenced list.txt file.

001 jane /data/jane_profile_07272018.doc
002 kwood /data/otherfile.doc
003 pchow /data/pfile.doc
004 janez /data/zfile.doc
005 jane /data/jane_pic_07282018.jpg
006 kwood /data/kwood.jpg
007 pchow /data/pchow.jpg
008 jane /data/jane_contact_07292018.csv
009 kwood /data/kwfile.csv
010 pchow /data/p2file.csv

findJaneResults

I know what I'm missing has to be simple, but what is it?

jlehenbauer
  • 599
  • 2
  • 11
  • 33
  • 3
    please don't use screenshots of code, but put the actual code in your question – Chris Maes Jan 31 '20 at 12:38
  • 1
    You have multiple quoting errors. Try http://shellcheck.net/ before asking for human assistance. – tripleee Jan 31 '20 at 12:40
  • 2
    `$f >> oldFiles.txt` Shouldn't that be preceded by a command like `echo` or `cat`? – Ruud Helderman Jan 31 '20 at 12:48
  • @RuudHelderman yes, I believe it should. I may think I had that in a previous version but forgot to replace it. – jlehenbauer Jan 31 '20 at 13:36
  • And my apologies @ChrisMaes, I'll replace it with code. – jlehenbauer Jan 31 '20 at 13:36
  • 1
    As you can see from the very error messages when running your program, ou don't have files where names start with a tilde. The statement `f=~$f` looks odd. – user1934428 Jan 31 '20 at 13:37
  • @user1934428 But the same commands run from the terminal (outside the script) are returning (with a tilde) that they exist. – jlehenbauer Jan 31 '20 at 14:02
  • @RuudHelderman you were right. I've fixed it on my side, but at this point, it's irrelevant to the question because that if statement is never being evaluated as true, which is what I can't figure out – jlehenbauer Jan 31 '20 at 14:07
  • Your file may have dos line endings. Please post text as text, not as image. Could you post the output like `<<<"$files" hexdump -C` from your script after assignment to `files`? Did you debug the script? Could you post the output of execution of your script with `set -x` enabled like `bash -x ./findJane.sh`? – KamilCuk Jan 31 '20 at 14:22
  • The remaining screen shot still obscures some fairly relevant details. Please [edit] your question to show a sample of the input file as text. – tripleee Jan 31 '20 at 14:42
  • 2
    You don't have a file named `~/foo`. You probably do have a file `$HOME/foo`. The interactive shell expands `~` to `$HOME`, but the script does not. Stop using `~` in your paths. – William Pursell Jan 31 '20 at 15:02
  • @WilliamPursell this seems to have been the issue! I went back and changed this reference as you said, and the files were found by the script. Would you mind posting this as an answer so I can accept it? Also, in my brief time with bash scripts, I've not been made privy to the use of `~` in scripts vs. commands, so if you'd care to elaborate as to the reasoning in your answer, that'd be great. Thanks!! – jlehenbauer Jan 31 '20 at 15:16
  • @jlehenbauer : Make sure that you try from the command line the exact same commands as in the script. `~/data` will work, if you have such an entry in your home directory, but if you have `f=/data`, `~$f` won't work, as William Pursell explained. – user1934428 Feb 01 '20 at 15:29

2 Answers2

1

Here's a refactoring which hopefully fixes most of the errors, with inline comments.

#!/bin/bash

# No need for this when you overwrite the file anyway
#> oldFiles.txt

# Don't capture a variable when you only need the result once
# POSIX calls this grep -F
grep -F ' jane ' ../data/list.txt |

# Let read -r do the splitting
#cut -d ' ' -f 3)

# Prefer while read over for
while read -r x y f; do
    # XXX what's this for?
    #f=~$f

    # Fix quoting
    if [ -e "$f" ]; then
        # Diagnostics to stderr
        echo "$0: $f Found" >&2
        # Redirect only once, below
        echo "$f"
    else
        # stderr
        echo "$0: $f Not found" >&2
    fi
# Redirect only once; overwrite
done >oldFiles.txt

If you want to look in your home directory and $f doesn't already have a leading slash, I guess the mystery line should be

    f=~/"$f"

If the file names contain a tilde already (as suggested by your screen shot) the shell will not expand that inside double quotes. You can fix this by manually expanding any leading tilde:

    case $f in
        '~/'*) f=~/"${f#~/}";;
    esac

The parameter expansion $[f#pattern} produces the value of $f with any prefix matching the glob expression pattern trimmed off from the beginning. (There's also ${f%pattern} for trimming from the end, and a good number of other convenient string manipulation facilities.)

If you want /data/stuff to search for a file $HOME/data/stuff or ./data/stuff then you have to add the leading component yourself. The path /data refers to a path directly in the root directory. Perhaps see also Difference between ./ and ~/ which has an answer by yours truly about this.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • Thanks for your style changes here, it's good to see how other people approach problems. Especially writing errors to stderr; I'm not used to bash yet and forget that diagnostics don't always just have to be `echo`d. It turns out the error was the `~`, which (I've now learned) shouldn't be used for paths in scripts. Also, wonderful commenting! – jlehenbauer Jan 31 '20 at 15:20
  • Thanks. See update now. – tripleee Jan 31 '20 at 15:37
0

Don't rely on tilde expansions in your script. In general, in your interactive shell, you can use ~ instead of $HOME since it's easier to type. bash has many other useful tilde expansions, such as ~foo (which expands to the home account of user foo), ~-, ~+, etc, but best practice is to only use these interactively. In your script, just use $HOME instead of ~.

I'd love to give a definitive answer, and the documentation states that no tilde expansion is done in posix mode, but I find that bash will sometimes do tilde expansions when I don't expect it. As a result of that missed expectation, I follow the general practice of simply never using ~ in scripts.

In this case, however, the problem is that bash is doing tilde expansion before variable expansion, and since there's no user named $f (note, that's the literal string $f, not the expansion of the variable f), the tilde prefix ~$f is unchanged by tilde expansion. Then variable expansion is applied and expands the $f, but the ~ remains unchanged. For example:

$ cat a.sh
#!/bin/sh

a=williamp
echo ~williamp
echo ~$a
$ ./a.sh
/Users/williamp
~williamp
William Pursell
  • 204,365
  • 48
  • 270
  • 300