3

Consider a directory with the following files:

file1 file1.suffix file2 file3 file3.suffix 

I need to list all files such that there doesn't exist another file having the same name and a known suffix. In the example above that would match only file2.

This is what i came up with:

diff --new-line-format= --unchanged-line-format= \ <(ls -I '*.suffix' | sort) \ <(ls *.suffix | sed 's/\(.*\)\.suffix/\1/' | sort) 

Is there a simpler and shorter way?

Edit 1

In my concrete case there's actually multiple suffixes (bind zone files /w dnssec):

example.org example.org.jbk example.org.jnl example.org.signed example.org.signed.jnl example.com 

I'm trying to list zones that don't have dnssec enabled, that is, files that don't have another file with .signed extension.

This is my attempt:

diff --new-line-format= --unchanged-line-format= \ <(ls -I '*.jbk' -I '*.jnl' -I '*.signed' -I '*.signed.jnl' | sort) \ <(ls *.signed | sed 's/\(.*\)\.signed/\1/' | sort) 
1
  • could be there files with other suffixes like file1.opt and how should they be treated? Commented Dec 11, 2017 at 13:16

3 Answers 3

4

With zsh:

setopt extendedglob # for the first "^" below ls -ld -- ^*.suffix(^e:'[[ -e $REPLY.suffix ]]':) 

(or use ^*.* instead of ^*.suffix if you only want to consider the extension-less files, or, following the update to your question *.(org|net|com) or ^*.*.* or ^*.(signed|jnl|jbk)...)

That is list the non-.suffix files, for which file.suffix doesn't exist using the e glob qualifier to select files based on the evaluation of some code (where $REPLY contains the path of the file to select).

Another approach using the ${a:|b} array subtraction operator (mnemonic: elements of a bar those of b):

bare=(^*.suffix(N)) with_suffix=(*.suffix(N:r)) ls -ld -- ${bare:|with_suffix} 

ls -ld -- is only used as an example command here, you can use any other command or store the list in an array like:

without_suffix=(${bare:|with_suffix}) 
4
  • I had a feeling zsh would have some clever magic to handle this. I didn't know about the e-qualifier and it seems quite powerful. Here's my modified version: ^*.(signed|jnl|jbk)(e:'[[ ! -f "$REPLY.signed" ]]':). Commented Dec 11, 2017 at 14:10
  • @Joe. That works too. [[ -e file ]] is for whether the file exists. -f adds the additional requirement that it be a regular file (and not other types of files like directory, socket, fifo, device...). If you only want to consider regular files, you could add the . glob qualifier (or -. to allow symlinks to regular files). Commented Dec 11, 2017 at 14:17
  • Oh right i didn't realize you can negate any qualifier with ^. That's handy! I think now it's the time to add extendedglob to my .zshrc. Commented Dec 12, 2017 at 6:19
  • @Joe, note that ^ to negate qualifiers doesn't require extendedglob, it's the ^ glob operator that does. Still extendedglob is useful for a variety of operators like # (equivalent of RE *), ## (RE +), (#i) (case insensitive), (#c3,9) (RE {3,9}), ^ (not), ~ (except), and some used primarily in pattern matching more than globs. Commented Dec 12, 2017 at 8:28
3

As your current files list has simple naming convention - here's short ls + cut + uniq approach:

ls -v1 * | cut -d'.' -f1 | uniq -u 
3
  • I love this. It's short, concise and outside the box, but unfortunately doesn't solve my actual problem (see edit). Commented Dec 11, 2017 at 13:36
  • @Joe Try listing only the .signed file plus one that's there for every zone (.jbk?): ls -v1 *.jbk *.signed | ... Commented Dec 11, 2017 at 14:10
  • 2
    @Joe: The following sed expression works with the second example: ls | sed -E ':a; s/\.(signed|jnl|jbk)$//; ta' | uniq -u Commented Dec 12, 2017 at 1:34
1

Don't know if shorter, but rather straightforwardly in the shell (Bash and zsh quickly tested):

for f in * ; do [[ $f != *.suffix ]] && ! [[ -f "$f.suffix" ]] && echo "$f" done 

(or replace echo with something sensible)

Or the same with multiple suffixes:

suffixes=(suffix postfix) for f in * ; do for s in "${suffixes[@]}" ; do [[ $f == *".$s" ]] || [[ -f "$f.$s" ]] && continue 2 done echo "$f" done 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.