2011-11-22 12 views
9

Ben bash, oldukça basit bir şey başarısız olmuş görünüyor. Bir dizinin tam yolunu tutan bir dize değişkeni var. İçinde son iki dizinleri başka bir dizeye atamak istiyorum. Örneğin, ben varsa:bash: bir yol adı için son iki dirs ayıklamak

DIRNAME = /a/b/c/d/e 

İsterdim:

DIRNAME2 = d/e 

ben yapacağım basit bash yapı veya sed komut var eminim ama beni kaçıyor. basename ya da dirname ya da bir adın aşırı kısımlarını iade etmeyen bir genelleştirilmiş sürümü gibi bir çeşit.

Teşekkürler! Dave

+1

Basename'ın basename ve dirname'lerini alıp bunları birleştiririm. –

cevap

7
DIRNAME=/a/b/c/d/e 
D2=$(dirname $DIRNAME) 
DIRNAME2=$(basename $D2)/$(basename $DIRNAME) 

Veya: Eğer mazoşizm içine ağır olmadıkça

DIRNAME2=$(basename $(dirname $DIRNAME))/$(basename $DIRNAME) 

arka tırnak o oyunu çalışmayın. Ve yollarda boşluklar varsa, değişken adının etrafında çift tırnak kullanın.

Bu, hemen hemen her kabukla çalışacaktır (hatta 7. Baskı Unix Bourne kabuğunda bile olsa). bash'da başka mekanizmalar da mevcut - diğer cevaplar bazı seçeneklerin bazılarını gösteriyor olsa da, expr aynı zamanda eski bir okul çözümü (7th Edition Unix'te de mevcuttu).

DIRNAME2=`basename \`dirname "$DIRNAME"\``/`basename "$DIRNAME"` 

(yuvalama İki düzeyleri çok korkunç değil; üç zor alır!)

+0

Vay, bu benim ilk gönderiimde stackoverflow ve ben de cevaplardan çok memnunum! Tek satırlı bir basename-dirname cevapına gittim, çünkü bu sadece bir betik, sayısal işler göndermenin bir parçası olarak çalışacağım. Ama bu ipliği bir sürü kaygan bash string hilesi için kaydedeceğim! – user1059256

+0

+1, bu en semantik çözümdür. – harpo

1

Ben globs kullanarak daha kısa bir yol olduğunu düşünüyorum, ama:

$ DIRNAME='a/b/c/d/e' 
$ LAST_TWO=$(expr "$DIRNAME" : '.*/\([^/]*/[^/]*\)$') 
$ echo $LAST_TWO 
d/e 
2

Özellikle yolları kırparak için bir yöntem bilmiyorum, ama kesinlikle bash's regular expression matching ile yapabilirsiniz:

DIRNAME=/a/b/c/d/e 
if [[ "$DIRNAME" =~ ([^/]+/+[^/]+)/*$ ]]; then 
    echo "Last two: ${BASH_REMATCH[1]}" 
else 
    echo "No match" 
fi 

Not: Buradaki yolu, yoldaki izin verilen ama olmayan bazı şeylerle başa çıkabilmek için beklediğinizden biraz daha karmaşık hale getirdim: bölü çizgileri kırpıyor ve birden fazla (gereksiz) eğik çizgiyi tolere ediyor son iki nam arasında es. Örneğin, "/ a/b/c // d //" üzerinde çalıştırmak "c // d" ile eşleşecektir. tek satırda

-1
function getDir() { 
echo $1 | awk -F/ ' 
{ 
    n=NF-'$2'+1; 
    if(n<1)exit; 
    for (i=n;i<=NF;i++) { 
     printf("/%s",$i); 
    } 
}' 
} 

dir="https://stackoverflow.com/a/b/c/d/e" 
dir2=`getDir $dir 1` 
echo $dir2 

Bu fonksiyondan son gelen dizinleri herhangi bir sayıda elde edebilirsiniz. davanız olarak çalıştırın için ,

dir2=`getDir $dir 2`; 

Çıktı:/d/e

1
DIRNAME="a/b/c/d/e" 
DIRNAME2=`echo $DIRNAME | awk -F/ '{print $(NF-1)"_"$(NF)}'` 

DIRNAME2 then has the value d_e 

Değişim her ne dilek alt çizgi.

8

Gereksiz işlemlerden kaçınmak için yerleşik yapıları elimden geldiğince kullanmayı tercih ederim. Çünkü betiğiniz Cygwin veya süreç oluşturma çok pahalı olan diğer işletim sistemleri altında çalıştırılabilir.

Ben sadece iki dizinleri ayıklamak istiyorsanız o kadar uzun olmadığını düşünüyorum:

base1="${DIRNAME##*/}" 
dir1="${DIRNAME%/*}" 
DIRNAME2="${dir1##*/}/$base1" 

Bu aynı zamanda başka komutlar yürütme katılan özel karakter sorunları önleyebilirsiniz.

+1

+1 En iyi çözüm IMHO. – helpermethod

0
dirname=/a/b/c/d/e 
IFS=/ read -a dirs <<< "$dirname" 
printf "%s/%s\n" "${dirs[-2]}" "${dirs[-1]}" 
İlgili konular