Самый короткий способ извлечь последние 3 символа основы (минус суффикс) имя файла

Вот решение для Perl. Это будет работать на любое количество файлов и ожидает, что первый файл будет списком. Это также добавит имя файла к заголовку FASTA.

#!/usr/bin/perl -w
use strict;
my $list=shift;
open(A,$list); 
my %k;
while(<A>){
    ## Remove trailing newline
    chomp;
    if ( /(\d+?)_knownids_(.+?)_.+?(\d+)$/){ 
      ## Concatenate the patterns and save in a hash
      my $pp=join("-", $1,$2,$3);
      $k{PAT}{$pp}=$_;
    }
}
close(A);
## Read each input file
my $name;
for my $f (@ARGV) {
    open(F,$f);
    while(<F>){
       ## Skip empty lines
       next if /^\s*$/;
       ## Is this a FASTA header?
       if ( /^\s*>/){
           ## If this id is in the list, keep it for this file
           if(/(\d+?)_knownids_(.+?)_.+?(\d+)$/){ 
              $name=join("-", $1,$2,$3);
           }
           ## Skip the sequences we are not interested in
           else{$name="foo"}
       }
       ## Collect the sequence
       else {
           if (defined($k{PAT}{$name})) {
           $k{$f}{$name}.=$_;
           }   
       } 
    }
    close(F);
}
## For each unique pattern found in list.txt
foreach my $pat (keys(%{$k{PAT}})) {
    ## For each of the files passed as arguments
    foreach my $file (@ARGV) {
    ## If the pattern was found in that file, print
    if (defined($k{$file}{$pat})) {
          print ">$k{PAT}{$pat}_$file\n";  
          print "$k{$file}{$pat}"
        }
    }
}

Если сценарий сохраняется как compare.pl, сделайте:

$ ./compare.pl list.txt sample1.txt sample2.txt sample3.txt sampleN.txt

Вывод:

> GETID_11084_knownids_3/3_Confidence_0.600_Length_1451_sample2.txt
sampletextforsample2
> GETID_17049_knownids_1/2_Confidence_0.625_Length_2532_sample1.txt
sampletextforsample1
> GETID_17049_knownids_1/2_Confidence_0.625_Length_2532_sample3.txt
sampletextforsample3
> GETID_15916_knownids_10/11_Confidence_0.324_Length_1825_sample3.txt
sample2textforsample3
12
20.03.2017, 12:18
7 ответов

Это типичное задание для expr:

$ file=/path/to/abcdef.txt
$ expr "/$file" : '.*\([^/.]\{3\}\)\.[^/.]*$'
def

Если вы знаете, что имена файлов имеют ожидаемый формат (содержит одну и только одну точку и не менее 3 символов перед точкой), то это можно упростить до:

expr "/$file" : '.*\(.\{3\}\)\.'

Обратите внимание, что статус выхода будет ненулевым, если совпадение отсутствует, а также если совпаденная часть является числом, которое разрешается до 0. (как для a000.txt или a-00. txt)

С помощью zsh:

file=/path/to/abcdef.txt
lastpart=${${file:t:r}[-3,-1]}

(:t для tail (имя базиса), :r для rest (с удалением расширения))).

.
6
27.01.2020, 19:54
var=123456
echo "${var#"${var%???}"}"

###OUTPUT###

456

Что сначала удаляет три последних символа из $var, а затем удаляет из $var результаты этого удаления - что возвращает три последних символа из $var. Вот несколько примеров, более конкретно направленных на демонстрацию того, как это можно сделать:

touch file.txt
path=${PWD}/file.txt
echo "$path"

/tmp/file.txt

base=${path##*/}
exten=${base#"${base%???}"}
base=${base%."$exten"}
{ 
    echo "$base" 
    echo "$exten" 
    echo "${base}.${exten}" 
    echo "$path"
}

file
txt
file.txt
/tmp/file.txt

Не обязательно распространять все это через столько команд. Вы можете сжать это:

{
    base=${path##*/} exten= 
    printf %s\\n "${base%.*}" "${exten:=${base#"${base%???}"}}" "$base" "$path"
    echo "$exten"
}

file 
txt 
file.txt 
/tmp/file.txt
txt

Комбинирование $IFS с setting параметрами оболочки также может быть очень эффективным средством для разбора и сверления переменных оболочки:

(IFS=. ; set -f; set -- ${path##*/}; printf %s "${1#"${1%???}"}")

В результате вы получите только три символа, непосредственно предшествующих первому периоду, следующему за последним / в $path. Если вы хотите получить только первые три символа, непосредственно предшествующие последнему . в $path (например, если существует возможность использования более одного . в имени файла):

(IFS=.; set -f; set -- ${path##*/}; ${3+shift $(($#-2))}; printf %s "${1#"${1%???}"}")

В обоих случаях вы можете сделать:

newvar=$(IFS...)

И...

(IFS...;printf %s "$2")

... распечатает то, что следует за .

Если вы не возражаете против использования внешней программы, вы можете сделать:

printf %s "${path##*/}" | sed 's/.*\(...\)\..*/\1/'

Если есть вероятность, что в имени файла \nимеется символ ewline (не применимо для нативных решений оболочки - все они все равно обрабатывают это):

printf %s "${path##*/}" | sed 'H;$!d;g;s/.*\(...\)\..*/\1/'
13
27.01.2020, 19:54

sed работает для этого:

[user@host ~]$ echo one.two.txt | sed -r 's|(.*)\..*$|\1|;s|.*(...)$|\1|'
two

Или

[user@host ~]$ sed -r 's|(.*)\..*$|\1|;s|.*(...)$|\1|' <<<one.two.txt
two

Если ваш sed не поддерживает -r, просто замените экземпляры () на \( и \), и тогда -r не понадобится.

2
27.01.2020, 19:54

Если perl доступен, я считаю, что он может быть более читабельным, чем другие решения, а именно потому, что его язык регексов более выразителен и имеет модификатор /x, который позволяет писать более четкие регексы:

perl -e 'print $1 if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"

Это ничего не печатает, если такого совпадения нет (если у базового имени нет расширения или если корень перед расширением слишком короткий). В зависимости от ваших требований, вы можете настроить регекс. Этот регекс обеспечивает соблюдение ограничений:

  1. Он соответствует 3 символам до последнего расширения (часть после и включая последнюю точку). Эти 3 символа могут содержать точку.
  2. Расширение может быть пустым (за исключением точки).
  3. Совпадающая часть и расширение должны быть частью базового имени (часть после последней косынки).

Использование этого в подстановке команд имеет обычные проблемы с удалением слишком большого количества трейлинговых новых строк, проблема, которая также влияет на ответ Стефана. Она может быть решена в обоих случаях, но здесь немного проще:

lastpart=$(
  perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x}  # allow for possible trailing newline
1
27.01.2020, 19:54

Python2. 7

$ echo /path/to/somefile.txt | python -c "import sys, os; print '.'.join(os.path.basename(sys.stdin.read()).split('.')[:-1])[-3:]"
ile

$ echo file.one.two.three | python -c "import sys, os; print '.'.join(os.path.basename(sys.stdin.read()).split('.')[:-1])[-3:]"
two
0
27.01.2020, 19:54

Я думаю, что эта функция bash, pathStr(), сделает то, что вы ищете.

Она не требует awk, sed, grep, perl или expr. Она использует только bash-строки, так что она довольно быстрая.

Я также включил зависимые функции argsNumber и isOption, но их функциональность может быть легко встроена в pathStr.

Зависимая функция ifHelpShow не включена, так как она имеет множество зависимостей для вывода текста справки либо в командной строке терминала, либо в GUI-диалоговом окне через YAD. Переданный ему текст справки включен в документацию. Сообщите, если вы хотите ifHelpShow и его зависимости.

function  pathStr () {
  ifHelpShow "$1" 'pathStr --OPTION FILENAME
    Given FILENAME, pathStr echos the segment chosen by --OPTION of the
    "absolute-logical" pathname. Only one segment can be retrieved at a time and
    only the FILENAME string is parsed. The filesystem is never accessed, except
    to get the current directory in order to build an absolute path from a relative
    path. Thus, this function may be used on a FILENAME that does not yet exist.
    Path characteristics:
        File paths are "absolute" or "relative", and "logical" or "physical".
        If current directory is "/root", then for "bashtool" in the "sbin" subdirectory ...
            Absolute path:  /root/sbin/bashtool
            Relative path:  sbin/bashtool
        If "/root/sbin" is a symlink to "/initrd/mnt/dev_save/share/sbin", then ...
            Logical  path:  /root/sbin/bashtool
            Physical path:  /initrd/mnt/dev_save/share/sbin/bashtool
                (aka: the "canonical" path)
    Options:
        --path  Absolute-logical path including filename with extension(s)
                  ~/sbin/file.name.ext:     /root/sbin/file.name.ext
        --dir   Absolute-logical path of directory containing FILENAME (which can be a directory).
                  ~/sbin/file.name.ext:     /root/sbin
        --file  Filename only, including extension(s).
                  ~/sbin/file.name.ext:     file.name.ext
        --base  Filename only, up to last dot(.).
                  ~/sbin/file.name.ext:     file.name
        --ext   Filename after last dot(.).
                  ~/sbin/file.name.ext:     ext
    Todo:
        Optimize by using a regex to match --options so getting argument only done once.
    Revised:
        20131231  docsalvage'  && return
  #
  local _option="$1"
  local _optarg="$2"
  local _cwd="$(pwd)"
  local _fullpath=
  local _tmp1=
  local _tmp2=
  #
  # validate there are 2 args and first is an --option
  [[ $(argsNumber "$@") != 2 ]]                        && return 1
  ! isOption "$@"                                      && return 1
  #
  # determine full path of _optarg given
  if [[ ${_optarg:0:1} == "/" ]]
  then
    _fullpath="$_optarg"
  else
    _fullpath="$_cwd/$_optarg"
  fi
  #
  case "$_option" in
   --path)  echo "$_fullpath"                            ; return 0;;
    --dir)  echo "${_fullpath%/*}"                       ; return 0;;
   --file)  echo "${_fullpath##*/}"                      ; return 0;;
   --base)  _tmp1="${_fullpath##*/}"; echo "${_tmp1%.*}" ; return 0;;
    --ext)  _tmp1="${_fullpath##*/}";
            _tmp2="${_tmp1##*.}";
            [[ "$_tmp2" != "$_tmp1" ]]  && { echo "$_tmp2"; }
            return 0;;
  esac
  return 1
}

function argsNumber () {
  ifHelpShow "$1" 'argsNumber "$@"
  Echos number of arguments.
  Wrapper for "$#" or "${#@}" which are equivalent.
  Verified by testing on bash 4.1.0(1):
      20140627 docsalvage
  Replaces:
      argsCount
  Revised:
      20140627 docsalvage'  && return
  #
  echo "$#"
  return 0
}

function isOption () {
  # isOption "$@"
  # Return true (0) if argument has 1 or more leading hyphens.
  # Example:
  #     isOption "$@"  && ...
  # Note:
  #   Cannot use ifHelpShow() here since cannot distinguish 'isOption --help'
  #   from 'isOption "$@"' where first argument in "$@" is '--help'
  # Revised:
  #     20140117 docsalvage
  # 
  # support both short and long options
  [[ "${1:0:1}" == "-" ]]  && return 0
  return 1
}

RESOURCES

0
27.01.2020, 19:54

Если вы можете использовать perl:

lastpart=$(
    perl -e 'print substr((split(/\.[^.]*$/,shift))[0], -3, 3)
            ' -- "$(basename -- "$1")"
)
4
27.01.2020, 19:54

Теги

Похожие вопросы