Score:0

Can't view file in parent directories with a script

gu flag

I have a script that looks for files in the present working directories and says if a file is a file or a directory

for file in $(ls | sort) ; do
        if [ -d $file ]
        then
                echo "$file is a directory"
        fi
        if [ -f $file ]
        then
                echo "$file is a file"
        fi
done

And that works fine. However, when I edit it to view the files in the parent directory I get no output. I changed this

for file in $(ls | sort) ; do

to this:

for file in $(ls .. | sort) ; do

Am I missing something here?

pa4080 avatar
cn flag
Hello. It is not recommended parse the output of `ls` within scripts. Try to use the Bash expansion - i.e. `for file in *; ...` or implement `find`. Also in my opinion it is absolutely necessary to quote the variable name especially when you are using the single bracket test within the if statement.
Score:3
jp flag

Suggestion

Shell globbing for filenames is the most effective and reliable way when listing files and directories in bash … Almost all other external file listing tools are prone to errors e.g. word splitting when processing their output in bash and they need special care/output formatting and error handling mechanisms implemented in your script to work with them reliably in bash … Their output might not be formatted to be ,by default, reliably processable in bash.

Therefore, …

To work on files and directories in the current directory, use:

for f in *; do
  if [ -d "$f" ]; then
    echo "$f is a directory"
  elif [ -f "$f" ]; then
    echo "$f is a file"
    fi
  done

Notice the double quotes(") around the parameter $f must be used inside the loop’s body(i.e. between the two command constructs do … done) but must not be used in the loop’s head(i.e. for f in *;).

To work on directories only, use:

for f in */; do ...

To work on parent directory contents, use:

for f in ../*; do ...

Or to limit to directories only in parent directory, use:

for f in ../*/; do ...

Notice that shell filename globbing in bash will be alphabetically and numerically sorted by default.

Notice that the output of ls will be sorted by default as well so you don't need to pipe it to sort the way you did in your example ... Please also refrain from parsing the output of ls that way unless you know the consequences ... Please see Why not parse ls (and what to do instead)?

Demonstration

To further understand the difference, please see the demonstration below:

Create these four files:

touch 3_with_tab$'\t'file.txt; touch 1_with_newline$'\n'file.txt; touch '2_with_space file.txt'; touch 4_normal_file.txt

List them using shell globbing ... Works perfectly:

for f in *; do [ -f "$f" ] && echo "$f is a file"; done
1_with_newline
file.txt is a file
2_with_space file.txt is a file
3_with_tab      file.txt is a file
4_normal_file.txt is a file

With find piped to a while loop ... Fails on filenames containing a new line:

find | while IFS= read -r f; do [ -f "$f" ] && echo "$f is a file"; done
./3_with_tab    file.txt is a file
./4_normal_file.txt is a file
./2_with_space file.txt is a file

With find in a for loop ... Fails on filenames containing spaces, tabs or newlines:

for f in $(find); do [ -f "$f" ] && echo "$f is a file"; done
./4_normal_file.txt is a file

With ls piped to a while loop ... Fails on filenames containing a new line:

ls | while IFS= read -r f; do [ -f "$f" ] && echo "$f is a file"; done
2_with_space file.txt is a file
3_with_tab      file.txt is a file
4_normal_file.txt is a file

With ls in a for loop ... Fails on filenames containing spaces, tabs or newlines:

for f in $(ls); do [ -f "$f" ] && echo "$f is a file"; done
4_normal_file.txt is a file

Mechanisms(find as example)

If, however, you must use an external file listing tool, then you need to pay attention formatting its output and reading it as input in bash … With find for example, your best bet is to use find's action -print0(NULL delimited output) piped to a while loop that makes use of read's option -d to set the input delimiter to NULL using the bash's ANSI-C quoting $'\0' like so:

find -print0 | while IFS= read -r -d $'\0' f; do [ -f "$f" ] && echo "$f is a file"; done
./3_with_tab    file.txt is a file
./4_normal_file.txt is a file
./1_with_newline
file.txt is a file
./2_with_space file.txt is a file

Or sorted with sort's option -z(line delimiter is NULL, not newline) like so:

find -print0 | sort -z | while IFS= read -r -d $'\0' f; do [ -f "$f" ] && echo "$f is a file"; done
./1_with_newline
file.txt is a file
./2_with_space file.txt is a file
./3_with_tab    file.txt is a file
./4_normal_file.txt is a file
Score:2
vn flag

Yup, you're missing something. You're listing files in the parent directory, but when you check whether it is a file or directory, you're still in the current directory - you have to modify it like this to work with parent directory:

for file in $(ls .. | sort) ; do
        if [ -d "../$file" ]
        then
                echo "$file is a directory"
        fi
        if [ -f "../$file" ]
        then
                echo "$file is a file"
        fi
done

However, as the comments indicate, this isn't necessarily the best way to go about this.

I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.