1

I want to write a for loop to obtain everything between the third / to the 4th . for a list of files.

My attempt:

for mcool_file in ./input/*.mcool; do
    while IFS= read -r id; do
        id | cut -d \\ -f 4- -d_ -f1-4
        # Do something
    done;
done

Traceback:

cut: only one type of list may be specified

Input

./../input/A001C007.hg38.nodups.pairs.mcool
./../input/A001C008.hg38.nodups.pairs.mcool

Desired output

A001C007
A001C008
1
  • if we can assume the files are roughly that format, then this could perhaps be restated as "from the third / to the first . after it". Or "from the last /", if you just want the filename part of the full path regardless of how long the path is. The way you put it now, something like ./a.b/c/x.y.z would give x.y and we'd need to count the dots that get removed from the left to know where to cut on the right.
    – ilkkachu
    May 23 at 13:25

4 Answers 4

3
for pathname in input/*.mcool; do
    basename "${pathname%%.*}"
done

This iterates over all names in the directory input that end in .mcool. For each pathname in $pathname, the pathname is truncated at the first dot using the standard parameter substitution ${pathname%%.*} (removes the longest suffix string matching the pattern .* from the value of $pathname), and then basename is used for extracting the filename portion of the pathname.

Testing:

$ tree
.
`-- input
    |-- A001C001.something.mcool
    |-- A001C002.something.mcool
    |-- A001C003.something.mcool
    |-- A001C004.something.mcool
    |-- A001C005.something.mcool
    |-- A001C006.something.mcool
    |-- A001C007.something.mcool
    |-- A001C008.something.mcool
    `-- A001C009.something.mcool

2 directories, 9 files
$ for pathname in input/*.mcool; do basename "${pathname%%.*}"; done
A001C001
A001C002
A001C003
A001C004
A001C005
A001C006
A001C007
A001C008
A001C009

This assumes that the first dot in $pathname occurs in the filename and not in the directory part of the pathname, which is why I don't start the pattern with ./.

But we can turn it around to allow for dots in the directory path too, by calling basename first:

for pathname in ./input/*.mcool; do
    name=$(basename "$pathname")
    printf '%s\n' "${name%%.*}"
done

If we know that that the suffix string we want to remove is exactly the string .something.mcool (or .hg38.nodups.pairs.mcool in your case), then the best solution would probably be something like

for pathname in ./input/*.something.mcool; do
    basename "$pathname" .something.mcool
done

... which uses basename to remove the known suffix from the pathname and return the filename portion of the pathname in one go, one pathname at a time.

With implementations of basename that support the non-standard -a and -s options (for handling multiple files and removing a fixed suffix string from each), you could even get away without using a loop at all, if there are not too many files to process:

$ basename -a -s .something.mcool ./input/*.something.mcool
A001C001
A001C002
A001C003
A001C004
A001C005
A001C006
A001C007
A001C008
A001C009

See the basename(1) manual on your system.

2
  • -a and -s are non-standard extensions, but that doesn't make the basename implementations that support them non-standard. May 23 at 17:26
  • @StéphaneChazelas Thanks. Sloppy use of words. Will fix.
    – Kusalananda
    May 23 at 18:49
1

With zsh:

print -rC1 -- input/*.mcool(N:t:r:r:r:r)

(where the :t modifier gets you the tail, and :r the root (removes an extension) of the file like in csh or vim).

Or:

set -o histsubstpattern
print -rC1 -- input/*.mcool(N:t:s/.*//)

Or:

(){print -rC1 -- ${@/.*}} input/*.mcool(N:t)

(using ksh-style ${var/pattern[/replacement]} instead of csh-style :s/foo/bar/, here on the arguments passed to an anonymous function, see also ${@%%.*} also from ksh).

0

If all input looks like

./../input/A001C007.hg38.nodups.pairs.mcool

then the probably easiest approach is:

start cmd:> sed -e 's+^./../input/++' -e 's/\..*$//' input
A001C007
A001C008

If you want everything including the third / be deleted and everything from the next . then

start cmd:> sed -r -e 's+^([^/]*/){3}++' -e 's/\..*$//' input
A001C007
A001C008

A general solution as you described it would have to count the number of . up to the third /.

0

If you know that it is the third /and fourth ., you may as well use awk and define fields according to / and . as delimiters:

awk -F'[./]' '{print $7}'
1
  • This works for the input given, when the delimiters are always in the order ./..//.. But if the input has slashes and dots in another sequence, it's not true that it's always the seventh field between the third / and fourth ..
    – ilkkachu
    May 23 at 13:28

You must log in to answer this question.

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