La forma más corta de extraer los últimos 3 caracteres del nombre de archivo base (menos el sufijo)

La forma más corta de extraer los últimos 3 caracteres del nombre de archivo base (menos el sufijo)

Estoy intentando establecer una variable en un script sh en los últimos 3 caracteres del nombre base de un archivo (por nombre base me refiero sin la rutaysin el sufijo). He logrado hacer esto pero, puramente por curiosidad, me pregunto si hay un comando único y más corto que pueda usar. Originalmente tenía una frase con awk, pero era bastante larga. Actualmente tengo este script de dos líneas (suponiendo que haya un nombre de archivo completo $1):

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

Así por ejemplo,"/ruta/a/algún archivo.txt"termina con"ile"en $lastpart.

¿Puedo combinar de alguna manera basenameel bit para eliminar el sufijo en un solo comando, y hay alguna manera de enviarlo tail(o algo más que pueda usar) sin usar una tubería? El sufijo es desconocido, por lo que no puedo basarlo como parámetro en basename.

En realidad, el objetivo principal no es tanto ser lo más breve posible, sino que sea lo más legible posible de un vistazo. El contexto real de todo esto esesta pregunta en superusuario, donde intento encontrar una respuesta razonablemente simple.

Respuesta1

var=123456
echo "${var#"${var%???}"}"

###OUTPUT###

456

Eso primero elimina los últimos tres caracteres de y $varluego los elimina de $varlos resultados de esa eliminación, lo que devuelve los últimos tres caracteres de $var. A continuación se muestran algunos ejemplos destinados más específicamente a demostrar cómo se puede hacer tal cosa:

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

No es necesario distribuir todo esto mediante tantos comandos. Puedes compactar esto:

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

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

La combinación $IFScon setlos parámetros de ting shell también puede ser un medio muy eficaz para analizar y profundizar en las variables de shell:

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

Esto le dará sólo los tres caracteres inmediatamente anteriores al primer punto que sigue al último /en $path. Si desea recuperar sólo los primeros tres caracteres inmediatamente anteriores al último .en$path (por ejemplo, si existe la posibilidad de que haya más de uno .en el nombre del archivo):

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

En ambos casos puedes hacer:

newvar=$(IFS...)

Y...

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

...imprimirá lo que sigue al.

Si no te importa usar un programa externo puedes hacer:

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

Si existe la posibilidad de que aparezca un \ncarácter ewline en el nombre del archivo(no aplicable para las soluciones de shell nativas; todas lo manejan de todos modos):

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

Respuesta2

Ese es un trabajo típico para expr:

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

Si sabe que los nombres de sus archivos tienen el formato esperado (contiene un solo punto y al menos 3 caracteres antes del punto), puede simplificarlo a:

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

Tenga en cuenta que el estado de salida será distinto de cero si no hay coincidencias, pero también si la parte coincidente es un número que se resuelve en 0 (como para a000.txto a-00.txt).

Con zsh:

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

( :tparacola(nombre base), :rparadescansar(sin extensión)).

Respuesta3

Si puedes usar perl:

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

Respuesta4

Si Perl está disponible, creo que puede ser más legible que otras soluciones, específicamente porque su lenguaje de expresiones regulares es más expresivo y tiene el /xmodificador, que permite escribir expresiones regulares más claras:

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

Esto no imprime nada si no existe dicha coincidencia (si el nombre base no tiene extensión o si la raíz antes de la extensión es demasiado corta). Dependiendo de sus requisitos, puede ajustar la expresión regular. Esta expresión regular impone las restricciones:

  1. Coincide con los 3 caracteres antes de la extensión final (la parte posterior al último punto incluido). Estos 3 caracteres pueden contener un punto.
  2. La extensión puede estar vacía (excepto el punto).
  3. La parte coincidente y la extensión deben ser parte del nombre base (la parte después de la última barra).

Usar esto en una sustitución de comandos tiene los problemas normales de eliminar demasiadas líneas nuevas al final, un problema que también afecta la respuesta de Stéphane. Se puede solucionar en ambos casos, pero aquí es un poco más fácil:

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

información relacionada