I have a large list of tuples of files in the format:

C_1.txt  <<

in a directory. As you can see, C_2.txt is missing from my list. I need to find a neat way of finding which of these files is missing their "partner", and print that file using bash. I think I need to modify this :

find $x -type f -printf '%f\n' | sort | uniq -c

to include {0:1} so it only searches the first 1 character of my list, and prints how many files start with that first character.

Expected output:

2  A
2  B
1  C
2  D
2  E

or (ideal) expected output:


2 Answers 2


Loop over the file names, extract the prefix (the string before the first _) and check how many files start with that prefix (I used set but you may as well use an array and check its length).
If it's only one, print its name:

for f in ./*.txt; do
set -- "${n}"_*
[ $# -eq 1 ] && printf '%s\n' "${f}"
set --

Pipe your file list to

sed -n '$!N;/\(.*\)1.txt\n\12.txt/!{P;D;}'

This always reads a pair of lines and if it's not a pair of something1.txt with something2.txt the orphan line is printed, so it's given your "ideal expected output".

Verbose explanation:

  • N appends the next line to the pattern space, so you have two lines, including the newline in between
  • /\(.*\)1.txt\n\12.txt/ is an "address", selecting whether the next command is to be executed. This could be a line number, a range, or in this case a regular expression that has to match the pattern space. .* can match any string and by surrounding it with \(.*\) we can later backreference it as \1. So \n\12 means a newline, followed by the string from the beginning, followed by a 2. Thus, we are searching for anystring1.txtanystring2.txt
  • The ! after the address inverts the match, so the following is only executed if the pattern space doesn't match the expression. This is the case if the line doesn't belong to a pair.
  • the {} form a block of commands, meaning that all commands inside are only executed if the pattern did match
  • P prints the pattern space upto the first newline, so only the first line is printed (because we don't know whether the second could belong to a pair).
  • finally D deletes the pattern space upto the first newline and the next cycle is started with the remaining line, which will again be tried to pair with the following one.

I hope this explanation helped you learn a bit of sed. *

  • Hmm. I get an error: sed: -e expression #1, char 30: unterminated address regex I tried a few different versions of this but the same thing happens. Even with: sed -n 'N;/(.*)1.txt\n\2.txt!{P;D;}' - which I think is what you meant? Sorry, I'm not very skilled with sed! Thanks for your answer though :)
    – tbs35
    Jul 20, 2017 at 3:08
  • I'm sorry, the / terminating the address was missing. Corrected my answer and added more explanation to improve your sed skills! (-:
    – Philippos
    Jul 20, 2017 at 5:54
  • Unless you know exactly what you're doing, always use $!N instead of N - your code fails to print the last file name if it's not paired due to the fact that N is the equivalent of q when there's no more input left. Jul 20, 2017 at 10:37
  • @don_crissti Of course, thank you. Edited. Although I believe you should always know what you are doing. By not knowing what you are doing, $!N can easily produce a script that never quits. (-:
    – Philippos
    Jul 20, 2017 at 12:44
  • How ? Can you post an example ? Jul 20, 2017 at 14:25

You must log in to answer this question.

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