O caminho mais curto para extrair os últimos 3 caracteres do nome de arquivo base (menos sufixo)

12

Estou tentando definir uma variável em um script sh para os 3 últimos caracteres do nome base de um arquivo (por nome base quero dizer sem o caminho e sem o sufixo). Eu consegui fazer isso, mas, puramente por curiosidade, estou me perguntando se existe um comando mais curto e único que eu possa usar. Originalmente eu tinha um one-liner com awk , mas foi bastante longo. Atualmente eu tenho este script de duas linhas (supondo que um nome de arquivo completo esteja em $1 ):

filebase='basename "$1"'
lastpart='echo -n ${filebase%.*} | tail -c3'

Por exemplo, "/ path / to / somefile.txt" acaba com "ile" em $lastpart .

Posso de alguma forma combinar basename e o bit para remover o sufixo em um único comando, e existe uma maneira de enviá-lo para tail (ou outra coisa que eu possa usar) sem usar um pipe? O sufixo é desconhecido, então não posso basear isso como um parâmetro para basename .

O objetivo principal não é tanto ser o mais curto quanto possível, para ser o mais legível possível. O contexto real de tudo isso é esta pergunta no Superusuário , onde eu ' Estou tentando chegar a uma resposta razoavelmente simples.

    
por Jason C 23.06.2014 / 06:27

7 respostas

6

Esse é um trabalho típico para expr :

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

Se você sabe que os nomes dos arquivos têm o formato esperado (contém um e apenas um ponto e pelo menos três caracteres antes do ponto), isso pode ser simplificado para:

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

Observe que o status de saída será diferente de zero se não houver correspondência, mas também se a parte correspondente for um número que resulte em 0. (como a000.txt ou a-00.txt )

com zsh :

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

( :t para tail (basename), :r para restante (com extensão removida)).

    
por 23.06.2014 / 16:14
13
var=123456
echo "${var#"${var%???}"}"

###OUTPUT###

456

Isso remove primeiro os três últimos caracteres de $var e, em seguida, remove de $var os resultados dessa remoção - o que retorna os três últimos caracteres de $var . Aqui estão alguns exemplos mais especificamente destinados a demonstrar como você pode fazer uma coisa dessas:

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

Você não precisa espalhar tudo isso por meio de tantos comandos. Você pode compactar isso:

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

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

A combinação dos parâmetros $IFS with set ting shell também pode ser um meio muito eficaz de analisar e analisar as variáveis do shell:

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

Isso fornecerá apenas os três caracteres imediatamente anteriores ao primeiro período após o último / em $path . Se você deseja recuperar apenas os três primeiros caracteres imediatamente anteriores ao último . em $path (por exemplo, se houver uma possibilidade de mais de um . no nome do arquivo) :

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

Em ambos os casos, você pode fazer:

newvar=$(IFS...)

E ...

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

... imprimirá o que segue o .

Se você não se importa em usar um programa externo, faça o seguinte:

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

Se houver uma chance de um caractere de \n ewline no nome de arquivo (não aplicável para as soluções de shell nativas - todas elas lidam com isso de qualquer maneira) :

printf %s "${path##*/}" | sed 'H;$!d;g;s/.*\(...\)\..*//'
    
por 23.06.2014 / 06:31
4

Se você puder usar perl :

lastpart=$(
    perl -e 'print substr((split(/\.[^.]*$/,shift))[0], -3, 3)
            ' -- "$(basename -- "$1")"
)
    
por 23.06.2014 / 10:05
2

sed funciona para isso:

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

Ou

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

Se o seu sed não for compatível com -r , basta substituir as instâncias de () por \( e \) e, em seguida, -r não será necessário.

    
por 23.06.2014 / 18:55
1

Se o perl estiver disponível, acho que ele pode ser mais legível do que outras soluções, especificamente porque sua linguagem regex é mais expressiva e possui o modificador /x , que permite escrever regexs mais claros:

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

Isso não imprime nada se não houver essa correspondência (se o nome da base não tiver extensão ou se a raiz antes da extensão for muito curta). Dependendo de suas necessidades, você pode ajustar o regex. Essa regex aplica as restrições:

  1. Corresponde aos 3 caracteres antes da extensão final (a parte depois de incluir o último ponto). Esses 3 caracteres podem conter um ponto.
  2. A extensão pode estar vazia (exceto o ponto).
  3. A parte correspondente e a extensão devem fazer parte do nome da base (a parte depois da última barra).

Usar isso em uma substituição de comando tem os problemas normais de remover muitas novas linhas, um problema que também afeta a resposta de Stéphane. Ele pode ser tratado em ambos os casos, mas é um pouco mais fácil aqui:

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

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
    
por 25.06.2014 / 15:57
0

Acho que essa função bash, pathStr (), fará o que você está procurando.

Não requer awk, sed, grep, perl ou expr. Ele usa apenas bash builtins por isso é bastante rápido.

Eu também incluí as funções argsNumber e isOption dependentes, mas suas funcionalidades poderiam ser facilmente incorporadas ao pathStr.

A função dependente ifHelpShow não está incluída, pois possui numerosas subdependências para a saída do texto de ajuda na linha de comando do terminal ou para uma caixa de diálogo da GUI via YAD . O texto de ajuda passado para ele é incluído para documentação. Aconselhar se você gostaria seHelpShow e seus dependentes.

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
}

RECURSOS

por 26.06.2014 / 22:55