기본 (접미사 빼기) 파일 이름의 마지막 3자를 추출하는 가장 짧은 방법


12

sh 스크립트에서 변수를 파일의 기본 이름의 마지막 3 자로 설정하려고합니다 (기본 이름은 경로 없고 접미사가 없음을 의미 합니다). 나는 이것을하는 데 성공했지만 순전히 호기심에서 사용할 수있는 더 짧은 단일 명령이 있는지 궁금합니다. 원래와 함께 하나의 라이너가 awk있었지만 다소 길었습니다. 현재이 두 줄 스크립트가 있습니다 (완전한 파일 이름이 있다고 가정 $1).

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

예를 들어 "/path/to/somefile.txt" 는에서 "ile" 로 끝납니다 $lastpart.

어떻게 든 basename접미사를 단일 명령으로 묶기 위해 비트와 비트를 결합 할 수 있습니까 tail? 파이프를 사용하지 않고 접미사를 보내 거나 사용할 수 있는 방법이 있습니까? 접미사를 알 수 없으므로에 매개 변수로 사용할 수 없습니다 basename.

주요 목표는 실제로 가능한 한 짧게 읽을 수있을 정도로 짧지는 않습니다. 이 모든 실제 상황은 수퍼 유저에 대한이 질문입니다 . 여기서 나는 간단한 대답을 제시하려고합니다.


2
같은 파일을 어떻게 다루 file.one.two.three겠습니까? 당신이 원하는 것 ile또는 two?
terdon

@terdon two이 작동합니다. 그 확장은 .three내가 추측 할 것 입니다.
Jason C

답변:


6

그것은 전형적인 직업입니다 expr.

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

파일 이름의 형식이 예상되는 경우 (점 앞에 하나의 점 하나와 최소한 3 자 포함) 다음과 같이 단순화 할 수 있습니다.

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

일치하는 항목이없는 경우에도 종료 상태는 0이 아니며 일치하는 부분이 0으로 해석되는 숫자 인 경우도 있습니다 (예 : a000.txt또는 a-00.txt).

zsh:

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

( :t대한 꼬리 (기본 이름) :r에 대한 나머지 (확장 제거와 함께)).


2
좋은. expr내가 익숙해 져야 할 또 하나입니다. 나는 일반적으로 솔루션을 정말로 좋아합니다 zsh( ${}어제 왼쪽에 중첩 된 대체에 대한 지원에 대해 읽고 sh있었고 똑같은 것을 원했습니다). 기본적으로 항상 존재하지는 않습니다.
Jason C

2
@JasonC-정보가 가장 중요합니다. 어쨌든 시스템의 요점을 최대한 활용하여 최대한 활용하십시오. 담당자가 음식을 사면 화를
낼 수

1
@mikeserv "요청 : 베이컨 교환 담당자"; 여기에 메타가 있습니다.
Jason C

1
@mikerserv는 POSIX이며 내장 기능 만 사용하며 프로세스를 포크하지 않습니다. 명령 대체를 사용하지 않으면 줄 바꿈 문자로 인한 문제를 피할 수 있으므로 좋은 대답입니다.
Stéphane Chazelas 2016 년

1
@ mikeserv, POSIX expr아니라는 것을 의미하지는 않았습니다 . 확실합니다. 그러나 거의 내장되어 있지 않습니다.
Stéphane Chazelas 2016 년

13
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

결합 $IFSset팅 쉘 파라미터도 파싱 쉘 변수 통해 시추 매우 효과적인 수단이 될 수있다 :

(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파일 이름에 줄 바꿈 문자 가있을 가능성이있는 경우 (기본 쉘 솔루션에는 해당되지 않음-모두 어쨌든 처리) :

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

1
감사합니다. 또한 documentation 을 찾았습니다 . 그러나 $base거기 에서 마지막 3자를 얻으려면 내가 할 수있는 최선은 3 줄 name=${var##*/} ; base=${name%%.*} ; lastpart=${base#${base%???}}이었습니다. 플러스 측면에서는 순수한 bash이지만 여전히 3 줄입니다. ( "/tmp/file.txt"의 예에서는 "file"대신 "ile"이 필요합니다.) 매개 변수 대체에 대해 많이 배웠습니다. 나는 그것이 그렇게 할 수 있다는 것을 몰랐다. .. 매우 편리하다. 나는 개인적으로도 읽을 수 있다는 것을 알았습니다.
Jason C

1
@JasonC-이것은 완전히 이식 가능한 동작입니다-bash와 관련이 없습니다. 나는 이것을 읽는 것이 좋습니다 .
mikeserv

1
글쎄, 나는 접미사를 제거하는 %대신 사용할 수 있으며 %%실제로 경로를 제거 할 필요가 없으므로 더 좋은 두 줄을 얻을 수 있습니다 noextn=${var%.*} ; lastpart=${noextn#${noextn%???}}.
Jason C

1
@JasonC-예, 작동하는 것처럼 보입니다. $IFS안에 ${noextn}있고 확장을 인용하지 않으면 중단됩니다 . 따라서 이것은 더 안전합니다 :lastpart=${noextn#"${noextn%???}"}
mikeserv

1
@ JasonC- 마지막, 위의 내용이 도움이된다면 이것을 보고 싶을 것이다 . 그것은 다른 형태의 매개 변수 확장을 다루며 그 질문에 대한 다른 대답도 정말 좋습니다. 그리고 같은 주제에 대한 다른 두 가지 답변에 대한 링크가 있습니다. 네가 원한다면.
mikeserv 2016 년

4

사용할 수있는 경우 perl:

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

그것은 멋지다. 새로운 투표를 받았습니다.
mikeserv

좀 더 간결합니다 : perl -e 'shift =~ /(.{3})\.[^.]*$/ && print $1' $filename. basename파일 이름에 접미사가 없지만 경로에있는 일부 디렉토리가 있으면 추가 파일이 필요합니다.
Dubu

@ Dubu : 파일 이름에 접미사가 없으면 솔루션이 항상 실패합니다.
cuonglm

1
@Gnouc 이것은 의도적 인 것입니다. 그러나 당신 말이 맞습니다. 이것은 목적에 따라 잘못 될 수 있습니다. 대안 :perl -e 'shift =~ m#(.{3})(?:\.[^./]*)?$# && print $1' $filename
Dubu

2

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필요하지 않습니다.


1

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

0

파이썬 2.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

나는이 bash 함수 pathStr ()이 당신이 찾고있는 것을 할 것이라고 생각합니다.

awk, sed, grep, perl 또는 expr이 필요하지 않습니다. 그것은 bash 내장만을 사용하므로 매우 빠릅니다.

또한 종속 argsNumber 및 isOption 함수를 포함했지만 해당 기능을 pathStr에 쉽게 통합 할 수 있습니다.

종속 함수 ifHelpShow는 터미널 명령 행 또는 YAD 를 통해 GUI 대화 상자에 도움말 텍스트를 출력하기위한 많은 하위 종속성이 있으므로 포함되지 않습니다 . 전달 된 도움말 텍스트는 설명서 용으로 포함되어 있습니다. 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
}

자원


이해가되지 않습니다-이미 bash이소 없이-완전히 이식 가능하게 비슷하게 수행하는 방법이 이미 데모되었습니다 . 또한 무엇 ${#@}입니까?
mikeserv

이것은 기능을 재사용 가능한 기능으로 패키지화합니다. re : $ {# @} ... 배열 및 해당 요소를 조작하려면 전체 변수 표기법 $ {}이 필요합니다. $ @는 인수의 '배열'입니다. $ {# @}은 인수 수에 대한 bash 구문입니다.
DocSalvager 2016 년

아니요, $#인수 수에 대한 구문이며 여기 다른 곳에서도 사용됩니다.
mikeserv

"$ #"은 "인수 개수"에 대해 광범위하게 문서화 된 systax 인 것이 맞습니다. 그러나 방금 "$ {# @}"이 동일하다는 것을 재확인했습니다. 나는 위치 인수와 배열의 차이점과 유사성을 실험 한 후에 그 점을 생각해 냈습니다. 후자는 배열 구문에서 나 왔으며, 이는 더 짧고 간단한 "$ #"구문의 동의어입니다. "$ #"을 사용하도록 argsNumber ()를 변경하고 문서화했습니다. 감사!
DocSalvager 2016 년

${#@}POSIX 사양 은 불행히도 매개 변수 확장 결과를 지정 $@하거나 $*지정 하지 않은 경우가 대부분 입니다. 작동 bash하지만 신뢰할 수있는 기능은 아닙니다. 제가 말하려는 것 같습니다.
mikeserv
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.