Bash에서 파일 이름 및 확장자 추출


2105

파일 이름 (확장자 없음)과 확장명을 별도로 가져오고 싶습니다.

지금까지 찾은 최고의 솔루션은 다음과 같습니다.

NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`

파일 이름에 여러 .문자 가 포함되어 있으면 작동하지 않기 때문에 잘못되었습니다 . ,의 내가이 말을 할 수 있다면 a.b.js, 그것은 고려할 것입니다 ab.js대신 a.b하고 js.

파이썬에서 쉽게 할 수 있습니다.

file, ext = os.path.splitext(path)

하지만 가능한 경우 파이썬 인터프리터를 실행하지 않는 것이 좋습니다.

더 좋은 아이디어가 있습니까?


이 질문은 이 bash 기술과 다른 여러 관련 기술을 설명합니다.
jjclarkson 2016 년

28
아래의 큰 응답을 적용 할 때 내가 여기에 보여처럼, 단순히 변수에 붙여 넣기하지 않는 잘못된 : extension="{$filename##*.}" 나는 잠시 동안처럼! $곱슬 머리 바깥쪽으로 이동 : 오른쪽 : extension="${filename##*.}"
Chris K

4
이것은 분명 사소한 문제이며 아래 답변이 완전히 정확한지 말하기는 어렵습니다. 이것이 (ba) sh에 내장 된 작업이 아니라는 것이 놀랍습니다 (응답은 패턴 일치를 사용하여 함수를 구현하는 것 같습니다). 파이썬의 사용하기로 결정 os.path.splitext... 대신에 위와을
피터 깁슨에게

1
으로 확장 표현해야 할 성격 의 파일을하는이 마법 자신의 성격과 offert 신성 파일을 확인 명령 표준 확장을 . 참조 내 대답
F. 하우리

2
OS와 유닉스 파일 시스템의 관점에서 보면 일반적으로 파일 확장자와 같은 것은 없습니다. "."사용 부품을 분리하는 것은 인간의 협약 이며, 인간이 그것을 따르기로 동의하는 한만 작동합니다. 예를 들어, 'tar'프로그램을 사용하면 출력 파일 이름을 "tar"로 지정하기로 결정했을 수 있습니다. 접두사 ".tar"대신 접두사- "somedir.tar"대신 "tar.somedir"제공 이 때문에 "일반적이고 항상 작동하는"솔루션은 없습니다. 특정 요구 사항과 예상되는 파일 이름과 일치하는 코드를 작성해야합니다.
CM

답변:


3499

먼저 경로없이 파일 이름을 가져옵니다.

filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"

또는 '.'대신 경로의 마지막 '/'에 초점을 맞출 수 있습니다. 예기치 않은 파일 확장자가 있어도 작동합니다.

filename="${fullfile##*/}"

설명서를 확인하고 싶을 수도 있습니다.


85
전체 기능 세트는 gnu.org/software/bash/manual/html_node/… 를 확인하십시오 .
D.Shawley 2016 년

24
"$ fullfile"에 따옴표를 추가하면 파일 이름이 손상 될 수 있습니다.
lhunath

47
도대체, filename = "$ {fullfile ## * /}"를 작성하고 추가 호출을 피할 수도 있습니다.basename
ephemient

45
파일에 확장자가없는 경우이 "솔루션"이 작동하지 않습니다. 대신 전체 파일 이름이 출력됩니다. 이는 확장자가없는 파일이 존재한다는 점을 고려하면 상당히 나쁩니다.
nccc

43
확장자가없는 파일 이름을 처리하도록 수정했습니다 extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo ''). 확장자 존재 하면 확장자 ( .예 :)를 포함하여 리턴됩니다 .txt.
mklement0

683
~% FILE="example.tar.gz"

~% echo "${FILE%%.*}"
example

~% echo "${FILE%.*}"
example.tar

~% echo "${FILE#*.}"
tar.gz

~% echo "${FILE##*.}"
gz

자세한 내용 은 Bash 매뉴얼의 쉘 매개 변수 확장 을 참조하십시오 .


22
.tar.gz와 같이 파일 이름의 "확장자"부분에 2 개의 점이 있으면 어떻게해야하는지에 대한 훌륭한 의문을 제기 할 수 있습니다. 가능한 모든 유효한 파일 확장자를 모른 채 해결할 수 없습니다.
rmeador

8
왜 해결할 수 없습니까? 필자의 예제에서는 파일에 개의 점이있는 확장자가 아니라 두 개의 확장자 가 포함되어 있다고 간주해야합니다 . 두 확장을 별도로 처리합니다.
Juliano

22
어휘 기준으로는 해결할 수 없으므로 파일 형식을 확인해야합니다. 당신이 게임을했고 당신이 dinosaurs.in.tar그것을 dinosaurs.in.tar.gz
zip으로

11
전체 경로를 통과하는 경우 더 복잡해집니다. 내 중 하나는 '.' 경로 중간에있는 디렉토리에는 있지만 파일 이름에는 없습니다. 예를 들어 "a / bc / d / e / filename"은 ".c / d / e / filename"을 시작합니다.
Walt Sellers

6
분명히 x.tar.gz확장자는 없으며 gz파일 이름은 확장자입니다 x.tar. 이중 확장과 같은 것은 없습니다. 나는 확실히 boost :: filesystem이 그것을 처리한다고 확신합니다. (분할 경로, change_extension ...) 그리고 그 행동은 내가 실수하지 않으면 파이썬을 기반으로합니다.
v.oddou

430

일반적으로 이미 확장명을 알고 있으므로 다음을 사용하려고 할 수 있습니다.

basename filename .extension

예를 들면 다음과 같습니다.

basename /path/to/dir/filename.txt .txt

그리고 우리는 얻는다

filename

60
로 그 두 번째 인수는 basename꽤 놀랄만, 타이 종류 선생님 / 부인 :입니다
akaIDIOT

10
이 기술을 사용하여 확장을 추출하는 방법은 무엇입니까? ;) 아, 기다려! 우리는 실제로 그것을 미리 모른다.
Tomasz Gandor

3
.zip또는로 끝나는 압축 된 디렉토리가 있다고 가정하십시오 .ZIP. 당신이 할 수있는 방법이 basename $file {.zip,.ZIP}있습니까?
Dennis

8
이것은 OP 질문의 일부에만 답변하지만 Google에 입력 한 질문에 답변합니다. :-) 매우 매끄러운!
sudo make

1
쉽고 POSIX 호환
gpanda

146

POSIX 매개 변수 확장의 마법을 사용할 수 있습니다.

bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo "${FILENAME%%.*}"
somefile
bash-3.2$ echo "${FILENAME%.*}"
somefile.tar

파일 이름이 형식 ./somefile.tar.gz이면 echo ${FILENAME%%.*}가장 긴 일치 항목을 탐욕스럽게 제거 .하고 빈 문자열을 사용 한다는 점에주의해야 합니다.

임시 변수를 사용하여 해결할 수 있습니다.

FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}

)


사이트 는 더 많은 것을 설명합니다.

${variable%pattern}
  Trim the shortest match from the end
${variable##pattern}
  Trim the longest match from the beginning
${variable%%pattern}
  Trim the longest match from the end
${variable#pattern}
  Trim the shortest match from the beginning

5
Joachim의 답변보다 훨씬 간단하지만 항상 POSIX 변수 대체를 찾아야합니다. 또한, 최대 OSX에이 실행이 곳 cut이없는 --complementsed가 없습니다 -r.
jwadsack

72

파일에 확장자가 없거나 파일 이름이 없으면 작동하지 않는 것 같습니다. 여기 내가 사용하는 것이 있습니다. 그것은 단지 내장을 사용하고 더 많은 (전부는 아님) 병적 인 파일 이름을 처리합니다.

#!/bin/bash
for fullpath in "$@"
do
    filename="${fullpath##*/}"                      # Strip longest match of */ from start
    dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename
    base="${filename%.[^.]*}"                       # Strip shortest match of . plus at least one non-dot char from end
    ext="${filename:${#base} + 1}"                  # Substring from len of base thru end
    if [[ -z "$base" && -n "$ext" ]]; then          # If we have an extension and no base, it's really the base
        base=".$ext"
        ext=""
    fi

    echo -e "$fullpath:\n\tdir  = \"$dir\"\n\tbase = \"$base\"\n\text  = \"$ext\""
done

다음은 몇 가지 테스트 사례입니다.

$ basename-and-extension.sh / / home / me / / home / me / file /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden / home / me / .hidden.tar / home / me / ...
/ :
    dir = "/"
    기본 = ""
    ext = ""
/ home / me / :
    dir = "/ home / me /"
    기본 = ""
    ext = ""
/ home / me / 파일 :
    dir = "/ home / me /"
    기본 = "파일"
    ext = ""
/home/me/file.tar :
    dir = "/ home / me /"
    기본 = "파일"
    ext = "tar"
/home/me/file.tar.gz :
    dir = "/ home / me /"
    base = "file.tar"
    ext = "gz"
/home/me/.hidden :
    dir = "/ home / me /"
    기본 = ".hidden"
    ext = ""
/home/me/.hidden.tar :
    dir = "/ home / me /"
    기본 = ".hidden"
    ext = "tar"
/ home / me / .. :
    dir = "/ home / me /"
    기본 = ".."
    ext = ""
. :
    dir = ""
    기본 = "."
    ext = ""

2
대신 dir="${fullpath:0:${#fullpath} - ${#filename}}"나는 종종 보았다 dir="${fullpath%$filename}". 작성하는 것이 더 간단합니다. 실제 속도 차이나 차이가 있는지 확실하지 않습니다.
dubiousjim

2
이것은 거의 항상 잘못된 #! / bin / bash를 사용합니다. 가능하면 #! / bin / sh를 선호하고 그렇지 않으면 #! / usr / bin / env bash를 선호하십시오.
좋은 사람

@Good Person : 거의 항상 틀렸다는 것을 모른다 : which bash-> /bin/bash; 아마도 당신의 배포판입니까?
vol7ron

2
@ vol7ron-많은 배포판에서 bash는 / usr / local / bin / bash에 있습니다. OSX에서 많은 사람들이 / opt / local / bin / bash에 업데이트 된 bash를 설치합니다. 따라서 / bin / bash가 잘못되었으므로 env를 사용하여 찾을 수 있습니다. / bin / sh 및 POSIX 구문을 사용하는 것이 더 좋습니다. solaris를 제외하고 이것은 POSIX 쉘입니다.
좋은 사람

2
@ GoodPerson 그러나 bash에 더 익숙하다면 왜 sh를 사용합니까? sh를 사용할 수 있는데 왜 Perl을 사용 하는가?
vol7ron

46

사용할 수 있습니다 basename.

예:

$ basename foo-bar.tar.gz .tar.gz
foo-bar

당신은 당신이 항상 실행되어 그러나 경우, 제거되어야한다 확장자베이스 이름을 제공해야합니까 tar함께 -z다음 확장 될 것이다라는 것을 알고있다 .tar.gz.

이것은 당신이 원하는 것을해야합니다 :

tar -zxvf $1
cd $(basename $1 .tar.gz)

2
cd $(basename $1 .tar.gz).gz 파일에서 작동 한다고 가정 합니다. 그러나 질문에 그는 언급했다Archive files have several extensions: tar.gz, tat.xz, tar.bz2
SS Hegde

Tomi Po는 2 년 전에 같은 내용을 게시했습니다.
phil294

안녕하세요 Blauhirn, 이것은 오래된 질문입니다. 나는 날짜에 어떤 일이 일어났다 고 생각합니다. 질문을받은 직후 질문에 답하고 다른 답변이 두 군데 만있는 곳을 분명히 기억합니다. 질문이 다른 질문과 병합되었을 수 있습니까? 그렇습니까?
Bjarke Freund-Hansen

그래, 나는 정확하게 기억한다. 나는 원래이 질문에 답했습니다. stackoverflow.com/questions/14703318/… 2 년 후에 요청한 날 에이 질문 에 병합되었습니다. 내 답변이 이런 식으로 이동했을 때 중복 답변으로 비난받을 수는 없습니다.
Bjarke Freund-Hansen

37
pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js

잘 작동하므로 다음을 사용할 수 있습니다.

pax> FILE=a.b.js
pax> NAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
pax> EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
pax> echo $NAME
a.b
pax> echo $EXTENSION
js

그런데 명령은 다음과 같이 작동합니다.

명령 NAME"."문자 다음에 "."줄 끝까지의 문자 이외의 숫자 를 아무 것도없이 바꿉니다 (즉, "."줄 끝에서 끝까지 모든 것을 제거 합니다). 이것은 기본적으로 정규식 속임수를 사용하는 욕심없는 대체입니다.

for 명령 은 줄 시작 부분 EXTENSION에 문자가 뒤 따르는 임의의 수의 문자 를 대체합니다 "."(즉, 줄 시작에서 마지막 점까지 포함하여 모든 항목을 제거함). 이것은 기본 조치 인 탐욕스러운 대체입니다.


확장명이없는 파일은 이름과 확장명에 동일하게 인쇄되므로 중단됩니다. 그래서 나는 sed 's,\.[^\.]*$,,'이름과 sed 's,.*\.,., ;t ;g'확장을 위해 사용합니다 ( 일반 명령 과 함께 비정형 testget명령을 사용합니다 substitute).
hiPyy

32

Mellen은 블로그 게시물에 대한 의견을 작성합니다.

Bash를 사용 ${file%.*}하면 확장명없이 파일 이름 ${file##*.}을 가져 오고 확장명 만 가져옵니다. 그건,

file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"

출력 :

filename: thisfile
extension: txt


29

필요가 신경 없습니다 awk또는 sed심지어 perl이 간단한 작업. os.path.splitext()매개 변수 확장 만 사용 하는 순수하고 호환 가능한 솔루션이 있습니다.

참조 구현

의 문서 os.path.splitext(path):

한 쌍의로 패스 경로를 분할 (root, ext)되도록 root + ext == path하고, 내선는 비어 있거나 점으로 시작하여 가장 한주기에 포함되어 있습니다. 기본 이름의 선행 기간은 무시됩니다. splitext('.cshrc')을 반환합니다 ('.cshrc', '').

파이썬 코드 :

root, ext = os.path.splitext(path)

배쉬 구현

선행 기간 존중

root="${path%.*}"
ext="${path#"$root"}"

선행 기간 무시

root="${path#.}";root="${path%"$root"}${root%.*}"
ext="${path#"$root"}"

테스트

다음은 선행 기간 무시 구현 의 테스트 사례이며 모든 입력에서 Python 참조 구현과 일치해야합니다.

|---------------|-----------|-------|
|path           |root       |ext    |
|---------------|-----------|-------|
|' .txt'        |' '        |'.txt' |
|' .txt.txt'    |' .txt'    |'.txt' |
|' txt'         |' txt'     |''     |
|'*.txt.txt'    |'*.txt'    |'.txt' |
|'.cshrc'       |'.cshrc'   |''     |
|'.txt'         |'.txt'     |''     |
|'?.txt.txt'    |'?.txt'    |'.txt' |
|'\n.txt.txt'   |'\n.txt'   |'.txt' |
|'\t.txt.txt'   |'\t.txt'   |'.txt' |
|'a b.txt.txt'  |'a b.txt'  |'.txt' |
|'a*b.txt.txt'  |'a*b.txt'  |'.txt' |
|'a?b.txt.txt'  |'a?b.txt'  |'.txt' |
|'a\nb.txt.txt' |'a\nb.txt' |'.txt' |
|'a\tb.txt.txt' |'a\tb.txt' |'.txt' |
|'txt'          |'txt'      |''     |
|'txt.pdf'      |'txt'      |'.pdf' |
|'txt.tar.gz'   |'txt.tar'  |'.gz'  |
|'txt.txt'      |'txt'      |'.txt' |
|---------------|-----------|-------|

시험 결과

모든 테스트를 통과했습니다.


2
아니요, 기본 파일 이름 text.tar.gz은 다음 text과 같습니다. 확장자는.tar.gz
frederick99

2
@ frederick99 내가 말했듯이 여기의 해결책 os.path.splitext은 파이썬에서 의 구현과 일치합니다 . 논란의 여지가있는 입력에 대해 그 구현이 제정신인지 여부는 또 다른 주제입니다.
Cyker

패턴 ( "$root") 내 따옴표는 어떻게 작동합니까? 생략하면 어떻게됩니까? (문제에 대한 문서를 찾을 수 없습니다.) 또한 파일 이름이 *있거나 파일 이름을 어떻게 처리 ?합니까?
ymett 2016 년

좋아, 나는 따옴표, 패턴 리터럴 예를 만드는 것이 프로그램을 테스트 *하고 ?특별하지 않다. 내 질문의 두 부분이 서로 대답합니다. 이것이 문서화되어 있지 않은 것이 맞습니까? 아니면 따옴표가 일반적으로 glob 확장을 비활성화한다는 사실로부터 이해되어야합니까?
ymett 2016 년

훌륭한 답변! 나는 루트를 계산하기위한 약간 더 간단한 변형을 제안 할 것이다 root="${path#?}";root="${path::1}${root%.*}".
Maëlan

26

cut명령을 사용하여 마지막 두 확장 ( ".tar.gz"부분) 을 제거 할 수 있습니다 .

$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo

의견에서 Clayton Hughes가 언급했듯이, 이것은 실제 질문의 예에서는 효과가 없습니다. 대안으로 다음 sed과 같이 확장 정규 표현식을 사용 하는 것이 좋습니다 .

$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1

무조건 마지막 두 (알파벳) 확장을 제거하여 작동합니다.

[Anders Lindahl의 의견 후 다시 업데이트]


4
파일 이름 / 경로에 다른 점이없는 경우에만 작동합니다. echo "mpc-1.0.1.tar.gz"| 컷 -d '.' --complement -f2-은 (단지 제 2 개 필드로 구분 한 후). "MPC-1 '생성
클레이튼 휴즈

@ClaytonHughes 당신은 정확하고 더 잘 테스트해야합니다. 다른 솔루션을 추가했습니다.
일부 프로그래머 친구

sed 표현식은 $일치하는 확장자가 파일 이름 끝에 있는지 확인하는 데 사용해야 합니다. 그렇지 않으면 파일 이름과 같은 i.like.tar.gz.files.tar.bz2예기치 않은 결과가 발생할 수 있습니다.
Anders Lindahl

@AndersLindahl 확장의 순서가 sed체인 순서 의 반대이면 여전히 그렇습니다 . 심지어와 $마지막에 파일 이름 등 mpc-1.0.1.tar.bz2.tar.gz을 모두 제거 .tar.gz하고 .tar.bz2.
일부 프로그래머 친구

$ echo "foo.tar.gz"| 컷 -d '.' -f2- WITHOUT --complement는 문자열 끝까지 두 번째 분할 항목을 가져옵니다. $ echo "foo.tar.gz"| 컷 -d '.' -f2- tar.gz
Gene Black

23

다음은 awk소프트웨어 패키지의 버전 번호 추출과 같은 고급 사용 사례를 포함한 대체 제안 (대부분 )입니다.

f='/path/to/complex/file.1.0.1.tar.gz'

# Filename : 'file.1.0.x.tar.gz'
    echo "$f" | awk -F'/' '{print $NF}'

# Extension (last): 'gz'
    echo "$f" | awk -F'[.]' '{print $NF}'

# Extension (all) : '1.0.1.tar.gz'
    echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'

# Extension (last-2): 'tar.gz'
    echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'

# Basename : 'file'
    echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'

# Basename-extended : 'file.1.0.1.tar'
    echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'

# Path : '/path/to/complex/'
    echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
    # or 
    echo "$f" | grep -Eo '.*[/]'

# Folder (containing the file) : 'complex'
    echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'

# Version : '1.0.1'
    # Defined as 'number.number' or 'number.number.number'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'

    # Version - major : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1

    # Version - minor : '0'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2

    # Version - patch : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3

# All Components : "path to complex file 1 0 1 tar gz"
    echo "$f" | awk -F'[/.]' '{$1=""; print $0}'

# Is absolute : True (exit-code : 0)
    # Return true if it is an absolute path (starting with '/' or '~/'
    echo "$f" | grep -q '^[/]\|^~/'

모든 사용 사례는 중간 결과에 의존하지 않고 원래 전체 경로를 입력으로 사용합니다.


20

허용 대답은 잘 작동하는 일반적인 경우 지만, 실패 에지 의 경우 , 즉 :

  • 확장자가없는 파일 이름 ( 이 답변의 나머지 부분에서 접미사 라고 함 )의 extension=${filename##*.}경우 빈 문자열이 아닌 입력 파일 이름을 반환합니다.
  • extension=${filename##*.}.컨벤션과 달리 초기는 포함하지 않습니다 .
    • 맹목적으로 접두사 .는 접미사가없는 파일 이름에는 작동하지 않습니다.
  • filename="${filename%.*}"입력 파일 이름으로 시작 하고 규칙에 위배되는 .추가 .문자 (예 .bash_profile:)를 포함하지 않으면 빈 문자열이됩니다 .

---------

따라서 모든 경우를 포괄 하는 강력한 솔루션 의 복잡성으로 인해 함수 가 필요 합니다. 아래의 정의를 참조하십시오. 경로의 모든 구성 요소를 반환수 있습니다 .

호출 예 :

splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'

입력 경로 뒤의 인수는 위치 변수 이름으로 자유롭게 선택 됩니다 .
관심 변수가 아닌 변수 앞에 나오지 않으려면 (쓰레기 변수 _사용 $_) 또는 ''; 예를 들어 파일 이름 루트와 확장자 만 추출하려면을 사용하십시오 splitPath '/etc/bash.bashrc' _ _ fnameroot extension.


# SYNOPSIS
#   splitPath path varDirname [varBasename [varBasenameRoot [varSuffix]]] 
# DESCRIPTION
#   Splits the specified input path into its components and returns them by assigning
#   them to variables with the specified *names*.
#   Specify '' or throw-away variable _ to skip earlier variables, if necessary.
#   The filename suffix, if any, always starts with '.' - only the *last*
#   '.'-prefixed token is reported as the suffix.
#   As with `dirname`, varDirname will report '.' (current dir) for input paths
#   that are mere filenames, and '/' for the root dir.
#   As with `dirname` and `basename`, a trailing '/' in the input path is ignored.
#   A '.' as the very first char. of a filename is NOT considered the beginning
#   of a filename suffix.
# EXAMPLE
#   splitPath '/home/jdoe/readme.txt' parentpath fname fnameroot suffix
#   echo "$parentpath" # -> '/home/jdoe'
#   echo "$fname" # -> 'readme.txt'
#   echo "$fnameroot" # -> 'readme'
#   echo "$suffix" # -> '.txt'
#   ---
#   splitPath '/home/jdoe/readme.txt' _ _ fnameroot
#   echo "$fnameroot" # -> 'readme'  
splitPath() {
  local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
    # simple argument validation
  (( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
    # extract dirname (parent path) and basename (filename)
  _sp_dirname=$(dirname "$1")
  _sp_basename=$(basename "$1")
    # determine suffix, if any
  _sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
    # determine basename root (filemane w/o suffix)
  if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # does filename start with '.'?
      _sp_basename_root=$_sp_basename
      _sp_suffix=''
  else # strip suffix from filename
    _sp_basename_root=${_sp_basename%$_sp_suffix}
  fi
  # assign to output vars.
  [[ -n $2 ]] && printf -v "$2" "$_sp_dirname"
  [[ -n $3 ]] && printf -v "$3" "$_sp_basename"
  [[ -n $4 ]] && printf -v "$4" "$_sp_basename_root"
  [[ -n $5 ]] && printf -v "$5" "$_sp_suffix"
  return 0
}

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

기능을 수행하는 테스트 코드 :

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

예상되는 출력-다음과 같은 경우에주의하십시오.

  • 접미사가없는 파일 이름
  • 로 시작하는 파일 이름 .( 접미사 시작으로 간주 되지 않음 )
  • 끝나는 입력 경로 /(트레일 링 /은 무시 됨)
  • 파일 이름 인 입력 경로 ( .부모 경로로 반환)
  • .-prefixed 토큰 보다 많은 파일 이름 (마지막 만 접미사로 간주 됨) :
----- /etc/bash.bashrc
parentpath=/etc
fname=bash.bashrc
fnameroot=bash
suffix=.bashrc
----- /usr/bin/grep
parentpath=/usr/bin
fname=grep
fnameroot=grep
suffix=
----- /Users/jdoe/.bash_profile
parentpath=/Users/jdoe
fname=.bash_profile
fnameroot=.bash_profile
suffix=
----- /Library/Application Support/
parentpath=/Library
fname=Application Support
fnameroot=Application Support
suffix=
----- readme.new.txt
parentpath=.
fname=readme.new.txt
fnameroot=readme.new
suffix=.txt

19

가장 작고 간단한 솔루션 (한 줄에)은 다음과 같습니다.

$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo

즉 A의 쓸모 사용echo . 일반적으로 결과를 표시하기 전에 출력에서 공백 토큰 화 및 와일드 카드 확장을 수행하도록 쉘이 특별히 요구하지 않는 한 echo $(command)간단히 작성하는 것이 좋습니다 . 퀴즈 : 결과는 무엇입니까 (그리고 그것이 정말로 원한다면 실제로 정말로 원합니다 ). commandcommandecho $(echo '*')echo *
tripleee

@triplee 나는 echo명령을 전혀 사용하지 않았다 . 방금 foo두 번째 줄의 결과로 세 번째 줄에 나타나는 결과를 보여주기 위해 사용했습니다 .
Ron

그러나 똑같이 basename "${file%.*}"할 것입니다. 명령 대체를 사용하여 출력을 캡처하고 echo동일한 출력으로 즉시 출력합니다. (인용
부호

또한 basename "$file" .txt매개 변수 대체의 복잡성을 피하십시오.
tripleee

1
@Ron 그의 시간을 낭비한다고 비난하기 전에 그의 첫 번째 논평을 읽으십시오.
frederick99

14

파일 이름 만 필요하면 다음을 시도해보십시오.

FULLPATH=/usr/share/X11/xorg.conf.d/50-synaptics.conf

# Remove all the prefix until the "/" character
FILENAME=${FULLPATH##*/}

# Remove all the prefix until the "." character
FILEEXTENSION=${FILENAME##*.}

# Remove a suffix, in our case, the filename. This will return the name of the directory that contains this file.
BASEDIRECTORY=${FULLPATH%$FILENAME}

echo "path = $FULLPATH"
echo "file name = $FILENAME"
echo "file extension = $FILEEXTENSION"
echo "base directory = $BASEDIRECTORY"

그리고 그것은 모두 = D입니다.


그냥 BASEDIRECTORY를 원했습니다 :) 감사합니다!
Carlos Ricardo

12

모든 필드와 -필드 번호에 추가 되는 후속 필드를 표시하도록 잘라내기를 수행 할 수 있습니다 .

NAME=`basename "$FILE"`
EXTENSION=`echo "$NAME" | cut -d'.' -f2-`

따라서 파일이 eth0.pcap.gz인 경우 확장은pcap.gz

동일한 논리를 사용하여 다음과 같이 cut과 함께 '-'를 사용하여 파일 이름을 가져올 수도 있습니다.

NAME=`basename "$FILE" | cut -d'.' -f-1`

확장명이없는 파일 이름에서도 작동합니다.


8

매직 파일 인식

이 스택 오버플로 질문에 대한 많은 좋은 답변 외에도 다음과 같이 추가하고 싶습니다.

Linux 및 기타 unixen에는 파일의 첫 바이트를 분석하여 파일 유형 감지를 수행 하는 매직 명령 file이 있습니다. 이것은 인쇄 서버에 사용되는 초기의 매우 오래된 도구입니다 (만약 작성되지 않은 경우 ... 확실하지 않습니다).

file myfile.txt
myfile.txt: UTF-8 Unicode text

file -b --mime-type myfile.txt
text/plain

표준 확장은 /etc/mime.types( 데비안 GNU / 리눅스 데스크탑 에서 찾을 수 있습니다 . man file그리고 man mime.types아마도 file유틸리티와 mime-support패키지 를 설치해야 합니다) :

grep $( file -b --mime-type myfile.txt ) </etc/mime.types
text/plain      asc txt text pot brf srt

당신은 만들 수 있습니다 올바른 확장을 결정하는 기능. 약간 (완벽하지 않은) 샘플이 있습니다.

file2ext() {
    local _mimetype=$(file -Lb --mime-type "$1") _line _basemimetype
    case ${_mimetype##*[/.-]} in
        gzip | bzip2 | xz | z )
            _mimetype=${_mimetype##*[/.-]}
            _mimetype=${_mimetype//ip}
            _basemimetype=$(file -zLb --mime-type "$1")
            ;;
        stream )
            _mimetype=($(file -Lb "$1"))
            [ "${_mimetype[1]}" = "compressed" ] &&
                _basemimetype=$(file -b --mime-type - < <(
                        ${_mimetype,,} -d <"$1")) ||
                _basemimetype=${_mimetype,,}
            _mimetype=${_mimetype,,}
            ;;
        executable )  _mimetype='' _basemimetype='' ;;
        dosexec )     _mimetype='' _basemimetype='exe' ;;
        shellscript ) _mimetype='' _basemimetype='sh' ;;
        * )
            _basemimetype=$_mimetype
            _mimetype=''
            ;;
    esac
    while read -a _line ;do
        if [ "$_line" == "$_basemimetype" ] ;then
            [ "$_line[1]" ] &&
                _basemimetype=${_line[1]} ||
                _basemimetype=${_basemimetype##*[/.-]}
            break
        fi
        done </etc/mime.types
    case ${_basemimetype##*[/.-]} in
        executable ) _basemimetype='' ;;
        shellscript ) _basemimetype='sh' ;;
        dosexec ) _basemimetype='exe' ;;
        * ) ;;
    esac
    [ "$_mimetype" ] && [ "$_basemimetype" != "$_mimetype" ] &&
      printf ${2+-v} $2 "%s.%s" ${_basemimetype##*[/.-]} ${_mimetype##*[/.-]} ||
      printf ${2+-v} $2 "%s" ${_basemimetype##*[/.-]}
}

이 함수는 나중에 사용할 수있는 Bash 변수를 설정할 수 있습니다.

(@Petesh 정답에서 영감을 얻음) :

filename=$(basename "$fullfile")
filename="${filename%.*}"
file2ext "$fullfile" extension

echo "$fullfile -> $filename . $extension"

8

알았으므로 올바르게 이해하면 여기에서 문제는 여러 확장자가있는 파일의 이름과 전체 확장자를 얻는 방법입니다 (예 :) stuff.tar.gz.

이것은 나를 위해 작동합니다 :

fullfile="stuff.tar.gz"
fileExt=${fullfile#*.}
fileName=${fullfile%*.$fileExt}

이것은 당신을 줄 것이다 stuff파일 이름으로하고 .tar.gz확장으로. 0을 포함하여 여러 확장 프로그램에서 작동합니다. 동일한 문제를 가진 사람에게 도움이되기를 바랍니다 =)


올바른 결과는에 따라 os.path.splitextOP가 원하는 결과 입니다 ('stuff.tar', '.gz').
Cyker

6

다음 스크립트를 사용합니다

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo

이것은 전혀 효율적이지 않습니다. 이 명령은 외부 명령이나 포크없이 순수 Bash에서 수행 될 수 있으므로 너무 많은 포크를 수행하는 것은 매우 불필요합니다.
codeforester

5
$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

이것은 파일 이름에 여러 개의 점과 공백을 제공하지만 확장자가 없으면 파일 이름 자체를 반환합니다. 그래도 확인하기 쉽습니다. 파일 이름과 확장자가 동일한 지 테스트하십시오.

당연히이 방법은 .tar.gz 파일에서는 작동하지 않습니다. 그러나 이는 2 단계 프로세스에서 처리 될 수 있습니다. 확장자가 gz이면 tar 확장자도 있는지 다시 확인하십시오.


5

물고기 에서 파일 이름과 확장자를 추출하는 방법 :

function split-filename-extension --description "Prints the filename and extension"
  for file in $argv
    if test -f $file
      set --local extension (echo $file | awk -F. '{print $NF}')
      set --local filename (basename $file .$extension)
      echo "$filename $extension"
    else
      echo "$file is not a valid file"
    end
  end
end

주의 사항 : 마지막 점으로 분할되며 점이있는 파일 이름에는 적합하지만 점이있는 확장명에는 적합하지 않습니다. 아래 예를 참조하십시오.

용법:

$ split-filename-extension foo-0.4.2.zip bar.tar.gz
foo-0.4.2 zip  # Looks good!
bar.tar gz  # Careful, you probably want .tar.gz as the extension.

이 작업을 수행하는 더 좋은 방법이있을 것입니다. 답변을 수정하여 개선하십시오.


처리 할 확장 세트가 제한되어 있고 모두 알고 있으면 다음을 시도하십시오.

switch $file
  case *.tar
    echo (basename $file .tar) tar
  case *.tar.bz2
    echo (basename $file .tar.bz2) tar.bz2
  case *.tar.gz
    echo (basename $file .tar.gz) tar.gz
  # and so on
end

이것은 않습니다 하지 첫 번째 예제로주의를 가지고 있지만, 당신이 기대할 수있는 얼마나 많은 확장에 따라 더 지루한 될 수 있도록 모든 경우를 처리 할 필요가 없습니다.


4

다음은 AWK 코드입니다 . 더 간단하게 할 수 있습니다. 그러나 나는 AWK에 좋지 않다.

filename$ ls
abc.a.txt  a.b.c.txt  pp-kk.txt
filename$ find . -type f | awk -F/ '{print $2}' | rev | awk -F"." '{$1="";print}' | rev | awk 'gsub(" ",".") ,sub(".$", "")'
abc.a
a.b.c
pp-kk
filename$ find . -type f | awk -F/ '{print $2}' | awk -F"." '{print $NF}'
txt
txt
txt

마지막 예제에서 첫 번째 awk 문이 필요하지 않습니까?
BHSPitMonkey 2016

다른 작업을 수행하여 Awk를 Awk로 파이프하는 것을 피할 수 있습니다 split(). awk -F / '{ n=split($2, a, "."); print a[n] }' uses /`를 최상위 구분 기호로 사용하지만 두 번째 필드를 분할 .하고 새 배열에서 마지막 요소를 인쇄합니다.
tripleee

4

간단히 사용 ${parameter%word}

귀하의 경우 :

${FILE%.*}

테스트하려면 다음 작업을 모두 수행하고 확장을 제거하십시오.

FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};

2
왜 공감해야합니까? =표지판 주위에 공백이 없어야하지만 여전히 유용 합니다.
SilverWolf-복원 Monica Monica

1
이것은 잘 작동합니다. 감사합니다! (이제 그것이 하향 조정 된 이유라면 등호 주위에 공백이 없습니다)
Alex. S.

3

Petesh 답변 에서 빌드 하면 파일 이름 만 필요한 경우 경로와 확장자를 한 줄로 제거 할 수 있습니다.

filename=$(basename ${fullname%.*})

나를 위해 작동하지 않았습니다 : "basename : 피연산자가 없습니다. 자세한 내용은 'basename --help'를 시도하십시오."
helmy

이상합니다. Bash를 사용하고 있습니까? 필자의 경우 3.2.25 (구 CentOS) 및 4.3.30 (데비안 Jessie) 버전 모두에서 완벽하게 작동합니다.
cvr

파일 이름에 공백이 있습니까? filename="$(basename "${fullname%.*}")"
Adrian

두 번째 인수 basename는 선택 사항이지만 제거 할 확장을 지정합니다. 대체는 여전히 유용하지만 basename실제로는 그렇지 않을 수도 있습니다. 실제로 쉘 내장으로 이러한 대체를 모두 수행 할 수 있기 때문입니다.
tripleee

3

크게 @ mklement0의 우수의 기반으로, 그리고 꽉 임의 유용의 bashisms - 이것뿐만 아니라 다른 답변 / 기타 질문 / "이놈의 인터넷이 있음을"... 내가 조금의 모든 것을 싸서 조금 더 이해할 수있는, 재사용 가능한 기능 내 (나)에 대한 .bash_profile(내가 생각) 무엇을 담당의보다 강력한 버전이어야합니다 dirname/ basename/ 당신이 무엇을 ..

function path { SAVEIFS=$IFS; IFS=""   # stash IFS for safe-keeping, etc.
    [[ $# != 2 ]] && echo "usage: path <path> <dir|name|fullname|ext>" && return    # demand 2 arguments
    [[ $1 =~ ^(.*/)?(.+)?$ ]] && {     # regex parse the path
        dir=${BASH_REMATCH[1]}
        file=${BASH_REMATCH[2]}
        ext=$([[ $file = *.* ]] && printf %s ${file##*.} || printf '')
        # edge cases for extensionless files and files like ".nesh_profile.coffee"
        [[ $file == $ext ]] && fnr=$file && ext='' || fnr=${file:0:$((${#file}-${#ext}))}
        case "$2" in
             dir) echo      "${dir%/*}"; ;;
            name) echo      "${fnr%.*}"; ;;
        fullname) echo "${fnr%.*}.$ext"; ;;
             ext) echo           "$ext"; ;;
        esac
    }
    IFS=$SAVEIFS
}     

사용 예 ...

SOMEPATH=/path/to.some/.random\ file.gzip
path $SOMEPATH dir        # /path/to.some
path $SOMEPATH name       # .random file
path $SOMEPATH ext        # gzip
path $SOMEPATH fullname   # .random file.gzip                     
path gobbledygook         # usage: -bash <path> <dir|name|fullname|ext>

1
잘 했어요; 몇 가지 제안 :-당신은 전혀 의존하지 않는 것 같습니다 $IFS(그렇다면 local설정의 효과를 현지화하는 데 사용할 수 있습니다 ). - local변수 를 사용하는 것이 좋습니다 . -오류 메시지가 (use )가 stderr아닌 으로 출력되어야하며 0이 아닌 종료 코드를 반환해야합니다. -이름을 바꾸는 것이 좋습니다 (전자는 dir 구성 요소가있는 경로를 제안합니다). - 원본이없는 경우에도 무조건 (기간)을 추가합니다 . 단순히 유틸리티 를 사용할 수 있지만 종료하는 것을 무시한다는 점에 유의하십시오 . stdout1>&2fullnamebasenamename.basename/
mklement0

2

간단한 대답 :

POSIX 변수 answer 에서 확장하려면 더 흥미로운 패턴을 수행 할 수 있습니다. 따라서 여기에 자세히 설명 된 경우 간단히 다음을 수행 할 수 있습니다.

tar -zxvf $1
cd ${1%.tar.*}

.tar의 마지막 항목이 잘립니다. <무언가> .

보다 일반적으로의 마지막 항목을 제거하려면 <무언가> . <무언가> 그 다음

${1.*.*}

잘 작동합니다.

위의 답변 링크가 죽은 것으로 보입니다. 다음은 TLDP의 Bash에서 직접 수행 할 수있는 문자열 조작에 대한 훌륭한 설명입니다 .


대소 문자를 구분하지 않고 일치시키는 방법이 있습니까?
tonix

2

확장명 을 허용 하려면 다음과 같이하십시오.

echo 'hello.txt' | sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION
echo 'hello.txt' | sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME

첫 번째 줄 설명 : PATH.EXT 또는 ANYTHING과 일치하고 EXT로 바꿉니다. ANYTHING이 일치하면 ext 그룹이 캡처되지 않습니다.


2

이것은 나를 위해 일한 유일한 것입니다.

path='folder/other_folder/file.js'

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

>> file

문자열 보간에도 사용할 수 있지만 불행히도 base미리 설정해야 합니다.


1

다음은 Bash 스크립트를 작성했을 때 파일 이름과 확장자를 찾는 데 사용한 알고리즘입니다.

#! /bin/bash 

#
# Finds 
# -- name and extension pairs
# -- null extension when there isn't an extension.
# -- Finds name of a hidden file without an extension
# 

declare -a fileNames=(
  '.Montreal' 
  '.Rome.txt' 
  'Loundon.txt' 
  'Paris' 
  'San Diego.txt'
  'San Francisco' 
  )

echo "Script ${0} finding name and extension pairs."
echo 

for theFileName in "${fileNames[@]}"
do
     echo "theFileName=${theFileName}"  

     # Get the proposed name by chopping off the extension
     name="${theFileName%.*}"

     # get extension.  Set to null when there isn't an extension
     # Thanks to mklement0 in a comment above.
     extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')

     # a hidden file without extenson?
     if [ "${theFileName}" = "${extension}" ] ; then
         # hidden file without extension.  Fixup.
         name=${theFileName}
         extension=""
     fi

     echo "  name=${name}"
     echo "  extension=${extension}"
done 

시운전.

$ config/Name\&Extension.bash 
Script config/Name&Extension.bash finding name and extension pairs.

theFileName=.Montreal
  name=.Montreal
  extension=
theFileName=.Rome.txt
  name=.Rome
  extension=.txt
theFileName=Loundon.txt
  name=Loundon
  extension=.txt
theFileName=Paris
  name=Paris
  extension=
theFileName=San Diego.txt
  name=San Diego
  extension=.txt
theFileName=San Francisco
  name=San Francisco
  extension=
$ 

참고 : 완전한 음역 프로그램 및 추가 테스트 사례는 여기에서 찾을 수 있습니다 : https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0


모든 솔루션에서 파일 확장자가없는 경우 빈 문자열을 반환하는 유일한 솔루션입니다.extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')
f0nzie

1

예제 /Users/Jonathan/Scripts/bash/MyScript.sh코드를 사용하면 이 코드는 다음과 같습니다.

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

가 발생합니다 ${ME}있는 MyScript${MY_EXT}.sh:


스크립트:

#!/bin/bash
set -e

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

echo "${ME} - ${MY_EXT}"

일부 테스트 :

$ ./MyScript.sh 
MyScript - .sh

$ bash MyScript.sh
MyScript - .sh

$ /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

$ bash /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

2
이것이 왜 다운 보트가 많은지 잘 모르겠습니다. 실제로 허용 된 답변보다 더 효율적입니다. (후자 는 확장명이 없는 입력 파일 이름으로도 중단됩니다 ). 명시적인 경로를 사용하는 basename것은 아마도 과잉 일 것입니다.
mklement0

1

위의 답변에서 가장 짧은 oneliner는 Python을 모방합니다.

file, ext = os.path.splitext(path)

파일에 실제로 확장자가 있다고 가정하면

EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT)

이것에 대해 투표를했습니다. 나는 사람들이 어떻게 든 싫어하는 답변을 제거하려고합니다.
commonpike

basename은 확장자 만 제거하지 않고 경로 만 제거합니다.
David Cullen

SUFFIX 옵션에 대해 잊어 버린 맨 페이지를 본 지 오래되었습니다.
David Cullen

무엇을 넣을지를 알기 전에 어느 확장을 벗겨 내야하는지 알아야 EXT합니다. (또한 개인 변수 이름은 대문자를 사용하지 않아야합니다. 시스템 변수를 위해 예약되어 있습니다.)
tripleee
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.