Bash의 경로 문자열에서 파일 접미사와 경로 부분을 어떻게 제거합니까?


396

과 같은 문자열 파일 경로가 주어지면 /foo/fizzbuzz.barbash를 사용하여 해당 fizzbuzz문자열 의 일부만 추출하는 방법은 무엇입니까?


정보 당신은에서 찾을 수 배쉬 설명서 에 대한 모양 ${parameter%word}${parameter%%word}부분 일치하는 부분을 후행한다.
1ac0

답변:


574

Bash의 # 및 % 연산자로 수행하는 방법은 다음과 같습니다.

$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz

${x%.bar}${x%.*}${x%%.*}뒤의 모든 것을 제거 하거나 첫 번째 점 뒤의 모든 것을 제거 하는 것일 수도 있습니다 .

예:

$ x="/foo/fizzbuzz.bar.quux"
$ y=${x%.*}
$ echo $y
/foo/fizzbuzz.bar
$ y=${x%%.*}
$ echo $y
/foo/fizzbuzz

설명서는 Bash 매뉴얼 에서 찾을 수 있습니다 . 찾으 ${parameter%word}${parameter%%word}부 정합 부 후단.


나는 이것이 가장 유연했기 때문에 이것을 사용하여 끝났고, 내가 잘하고 싶었던 것과 비슷한 몇 가지 다른 일들이있었습니다.
Lawrence Johnston

이것은 아마도 게시 된 모든 답변 중 가장 유연하지만 basename 및 dirname 명령을 제안하는 답변에도주의를 기울여야한다고 생각합니다. 다른 멋진 패턴 일치가 필요하지 않은 경우 트릭 일 수 있습니다.
mgadda December

7
이것을 $ {x % .bar}라고합니다. 그것에 대해 더 배우고 싶습니다.
바질

14
@Basil : 파라미터 확장. 콘솔에서 "man bash"를 입력 한 다음 "/ parameter expansion"을 입력하십시오.
Zan Lynx

1
'man bash'설명은 이미 무엇을하는지 알고 있거나 어려운 방법으로 직접 시도해 보았을 때 의미가 있다고 생각합니다. git reference만큼 나쁘다. 대신 Google에 표시합니다.
triplebig

226

basename 명령을보십시오 :

NAME="$(basename /foo/fizzbuzz.bar .bar)"

11
아마도 현재 제공되는 모든 솔루션 중 가장 간단한 것입니다 ...하지만 백틱 대신 $ (...)을 사용할 것입니다.
마이클 존슨

6
가장 단순하지만 의존성을 추가합니다 (거대하거나 이상한 것은 아닙니다). 또한 접미사를 알아야합니다.
Vinko Vrsalovic

그리고 끝에서 무엇이든 제거하는 데 사용할 수 있습니다. 기본적으로 끝에서 문자열을 제거합니다.
Smar

2
문제는 시간 히트입니다. 방금 bash가 basename을 사용하여 800 파일을 처리하는 데 거의 5 분이 걸리는 것을 본 후에이 토론에 대한 질문을 검색했습니다. 상기 정규식 방법을 사용하여 시간을 약 7 초로 줄였다. 이 답변은 프로그래머가 수행하기 쉽지만 시간이 너무 많이 걸립니다. 수천 개의 파일이 들어있는 폴더를 상상해보십시오! 그런 폴더가 있습니다.
xizdaqrian

@xizdaqrian 이것은 절대적으로 거짓입니다. 이것은 간단한 프로그램으로 돌아 오는 데 0.5 초가 걸리지 않습니다. 방금 찾은 시간 찾기 / home / me / dev -name "* .py".py -exec basename {} \; 그리고 1 초에 1500 개의 파일에 대한 확장자와 디렉토리를 제거했습니다.
Laszlo Treszkai

40

두 가지 별도 작업으로 수행되는 순수 bash :

  1. 경로 문자열에서 경로를 제거하십시오.

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. 경로 문자열에서 확장을 제거하십시오.

    base=${file%.*}
    #${base} is now 'file'.

18

basename을 사용하여 이것을 달성하기 위해 다음을 사용했습니다.

for file in *; do
    ext=${file##*.}
    fname=`basename $file $ext`

    # Do things with $fname
done;

파일 확장자에 대한 사전 지식이 필요하지 않으며 파일 이름에 점이있는 파일 이름이있는 경우에도 작동합니다 (확장자 앞). 프로그램이 필요 basename하지만 이것은 GNU coreutils의 일부이므로 배포판과 함께 제공되어야합니다.


1
훌륭한 답변! 확장 프로그램을 매우 깔끔하게 제거하지만을 제거하지는 않습니다. 파일 이름 끝에
metrix

3
@metrix는 그냥 "."를 추가하십시오. $ ext 전에, 즉 : fname=`basename $file .$ext`
Carlos Troncoso

13

순수한 배쉬 방법 :

~$ x="/foo/bar/fizzbuzz.bar.quux.zoom"; 
~$ y=${x/\/*\//}; 
~$ echo ${y/.*/}; 
fizzbuzz

이 기능은 man bash에서 "Parameter Expansion"에 설명되어 있습니다. 비 배쉬 방법은 풍부합니다 : awk, perl, sed 등.

편집 : 파일 접미사의 점으로 작동 하며 접미사 (확장자)를 알 필요 는 없지만 이름 자체의 점으로는 작동 하지 않습니다 .


11

basename 및 dirname 함수는 다음과 같습니다.

mystring=/foo/fizzbuzz.bar
echo basename: $(basename "${mystring}")
echo basename + remove .bar: $(basename "${mystring}" .bar)
echo dirname: $(dirname "${mystring}")

출력 :

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo

1
따옴표를 여기에서 수정하는 것이 도움이 될 것입니다. 현재 상수 값 이 아닌 shellcheck.net 을 통해 이를 실행하면 mystring=$1(여러 경고를 억제하고 공백 / 글로브 문자 등을 포함하지 않아야 함) 문제를 해결할 수 있습니다 발견?
Charles Duffy

글쎄, 나는 $ mystring에서 인용 부호를 지원하기 위해 약간의 변경을했다.
어이

결과를 인용하면 더 향상 될 것입니다 : echo "basename: $(basename "$mystring")"- 완료 현재 디렉토리의 파일 목록으로 바꾸지 mystring='/foo/*'않으면 그렇게하십시오 . * basename
Charles Duffy

6

Using basename는 파일 확장자가 무엇인지 알고 있다고 가정합니까?

그리고 다양한 정규식 제안이 하나 이상의 ""를 포함하는 파일 이름을 처리하지 못한다고 생각합니다.

다음은 이중 점에 대처하는 것 같습니다. 아, "/"자체를 포함하는 파일 이름 (킥만)

파스칼의 말을 인용하자면 "죄송합니다.이 스크립트는 너무 길어요.


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 

1
이것은 훌륭하고 강력합니다
Gaurav Jain

4

또한받는 POSIX 준수 구문 에서 사용 이 응답 ,

basename string [suffix]

에서와 같이

basename /foo/fizzbuzz.bar .bar

GNUbasename 는 다른 구문을 지원합니다.

basename -s .bar /foo/fizzbuzz.bar

같은 결과로. 차이점과 장점은 여러 인수를 지원 하는 -simplies입니다 -a.

$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar

-z옵션 은 옵션을 사용하여 출력을 NUL 바이트로 분리하여 파일 이름을 안전하게 만들 수도 있습니다 ls.

$ ls has*
'has'$'\n''newline.bar'  'has space.bar'  'has*.bar'

배열로 읽기 :

$ readarray -d $'\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=$'has\nnewline' [1]="has space" [2]="has*")

readarray -dBash 4.4 이상이 필요합니다. 이전 버전의 경우 다음을 반복해야합니다.

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)

또한 지정된 접미사가 있으면 출력에서 제거되고 그렇지 않으면 무시됩니다.
aksh1618


3

다른 게시물에서 제안한대로 기본 이름을 사용할 수없는 경우 언제든지 sed를 사용할 수 있습니다. 다음은 (못생긴) 예입니다. 가장 크지는 않지만 원하는 문자열을 추출하고 입력을 원하는 문자열로 대체하여 작동합니다.

echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'

어떤 결과를 얻을 수 있습니까?

피즈 버즈


이것이 원래 질문에 대한 대답이지만,이 명령은 파일에 경로를 가지고 기본 이름을 추출하여 화면에 인쇄 할 때 유용합니다.
최상철

2

제안 된 펄 솔루션에주의하십시오 : 첫 번째 점 뒤의 모든 것을 제거합니다.

$ echo some.file.with.dots | perl -pe 's/\..*$//;s{^.*/}{}'
some

펄로하고 싶다면 다음과 같이하십시오.

$ echo some.file.with.dots | perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'
some.file.with

하지만 당신은 강타와 솔루션을 사용하는 경우 y=${x%.*}(또는 basename "$x" .ext당신이 확장을 알고있는 경우에) 훨씬 더 간단합니다.


1

기본 이름으로 경로를 제거합니다. 주어진 접미사와 파일의 접미사와 일치하는 경우 접미사도 제거하지만 명령에 제공 할 접미사를 알아야합니다. 그렇지 않으면 mv를 사용하여 다른 이름이 무엇인지 알아낼 수 있습니다.


1

전체 경로없이 파일 이름을 얻으려면 최상위 답변과 두 번째 최상위 답변을 결합하십시오.

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz

왜 여기서 배열을 사용하고 있습니까? 또한 왜 basename을 사용합니까?
codeforester
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.