รับพาเรนต์ของไดเร็กทอรีใน Bash


207

หากฉันมีเส้นทางไฟล์เช่น ...

/home/smith/Desktop/Test
/home/smith/Desktop/Test/

ฉันจะเปลี่ยนสตริงเพื่อให้เป็นไดเรกทอรีหลักได้อย่างไร

เช่น

/home/smith/Desktop
/home/smith/Desktop/

4
คุณสามารถใช้ ' ..' แต่บางทีนั่นอาจไม่ใช่สิ่งที่คุณคิด
Jonathan Leffler

คำตอบ:


331
dir=/home/smith/Desktop/Test
parentdir="$(dirname "$dir")"

ใช้งานได้หากมีเครื่องหมายทับต่อท้ายเช่นกัน


14
คำพูดที่อยู่ข้างในนั้นมีความสำคัญหรือคุณจะปล่อยข้อมูลทั้งหมดของคุณออกไปหมด
catamphetamine

10
และตัวแปรที่ฉันใช้เพื่อรับพาเรนต์ของไดเร็กทอรีการทำงานปัจจุบัน: parentdir=$(dirname `pwd`)
TheGrimmScientist

3
หมายเหตุวิธีนี้ไม่ปลอดภัยสำหรับชื่อ "น่าเกลียด" ชื่อเช่น "-rm -rf" จะทำให้พฤติกรรมที่ต้องการพัง อาจเป็น "." จะเช่นกัน ฉันไม่ทราบว่าเป็นวิธีที่ดีที่สุดหรือไม่ แต่ฉันได้สร้างคำถาม "เน้นความถูกต้อง" แยกไว้ที่นี่: stackoverflow.com/questions/40700119/…
VasiliNovikov

4
@Christopher Shroba ใช่ฉันไม่ได้ใส่เครื่องหมายคำพูดแล้ว HDD ของฉันก็ถูกเช็ดบางส่วน ฉันจำไม่ได้ว่าเกิดอะไรขึ้น: น่าจะเป็นไดเรกทอรี whitespaced ที่ไม่มีคำตอบทำให้ลบ dir พาเรนต์แทนที่จะเป็นเป้าหมาย - สคริปต์ของฉันเพิ่งลบโฟลเดอร์ / home / xxx /
catamphetamine

3
โอ้ไม่เป็นไรดังนั้นจึงไม่มีอะไรเกี่ยวกับคำสั่งที่จะทำให้ข้อมูลสูญหายหากคุณไม่ได้ใช้คำสั่งลบใช่ไหม มันเป็นแค่เรื่องของเส้นทางที่คุณพยายามจะแยกออกจากการแยกตัวละครในอวกาศและกำจัดมากกว่าที่คาดไว้หรือไม่?
Christopher Shroba

18

... แต่สิ่งที่ "เห็นที่นี่ " แตกหัก นี่คือการแก้ไข:

> pwd
/home/me
> x='Om Namah Shivaya'
> mkdir "$x" && cd "$x"
/home/me/Om Namah Shivaya
> parentdir="$(dirname "$(pwd)")"
> echo $parentdir
/home/me

14

เพียงใช้echo $(cd ../ && pwd)ในขณะที่ทำงานในไดเรกทอรีที่มีผู้ปกครองคุณต้องการค้นหา โซ่นี้ยังมีประโยชน์เพิ่มเติมของการไม่มีเครื่องหมายทับ


คุณไม่ต้องการechoที่นี่
gniourf_gniourf

14

เห็นได้ชัดว่าไดเรกทอรีหลักจะได้รับจากการต่อท้ายชื่อไฟล์ dot-dot:

/home/smith/Desktop/Test/..     # unresolved path

แต่คุณต้องต้องการพา ธ ที่ถูกแก้ไข (พา ธ สัมบูรณ์โดยไม่มีคอมโพเนนต์จุดพา ธ ):

/home/smith/Desktop             # resolved path

ปัญหาของคำตอบยอดนิยมที่ใช้dirnameคือพวกเขาไม่ทำงานเมื่อคุณป้อนเส้นทางด้วยจุดจุด:

$ dir=~/Library/../Desktop/../..
$ parentdir="$(dirname "$dir")"
$ echo $parentdir
/Users/username/Library/../Desktop/..   # not fully resolved

นี่มีประสิทธิภาพมากขึ้น :

dir=/home/smith/Desktop/Test
parentdir=`eval "cd $dir;pwd;cd - > /dev/null"`

คุณสามารถป้อนได้/home/smith/Desktop/Test/..แต่เส้นทางที่ซับซ้อนเช่น:

$ dir=~/Library/../Desktop/../..
$ parentdir=`eval "cd $dir;pwd;cd - > /dev/null"`
$ echo $parentdir
/Users                                  # the fully resolved path!

แก้ไข: หากมันใช้งานไม่ได้ให้ตรวจสอบว่าคุณcdยังไม่ได้ทำการแก้ไขเพราะบางครั้งก็เป็นเรื่องธรรมดา

$ which cd
cd is a function  #cd was modified to print extra info, so use "builtin cd" to override
cd ()
{
    builtin cd "$@";
    ls -FGAhp
}
...

การใช้evalไม่ดีถ้ามีโอกาสในการแยกวิเคราะห์อินพุตของผู้ใช้ที่อาจทำให้คุณอยู่ในที่ที่คุณไม่คาดหวังหรือเรียกใช้สิ่งที่ไม่ดีต่อระบบของคุณ คุณสามารถใช้pushd $dir 2>&1 >/dev/null && pwd && popd 2>&1 >/dev/nullแทนeval?
dragon788

2
คุณไม่จำเป็นต้องevalที่ทั้งหมด: parentdir=$(cd -- "$dir" && pwd)เพียงแค่ทำ เนื่องจากcdมีการเรียกใช้ใน subshell คุณไม่จำเป็นต้องcdกลับไปที่ที่เราอยู่ --อยู่ที่นี่ในกรณีที่การขยายตัวของ$dirการเริ่มต้นด้วยเครื่องหมายขีดกลาง &&เป็นเพียงการป้องกันไม่parentdirให้มีเนื้อหาที่ไม่ว่างเปล่าเมื่อcdไม่ suceed
gniourf_gniourf

8

แรงจูงใจสำหรับคำตอบอื่น

ฉันชอบรหัสที่สั้นมากชัดเจนและรับประกันได้ จุดโบนัสถ้ามันไม่ได้รันโปรแกรมภายนอกตั้งแต่วันที่คุณต้องการประมวลผลรายการจำนวนมากมันจะเร็วขึ้นอย่างเห็นได้ชัด

หลัก

ไม่แน่ใจเกี่ยวกับสิ่งที่รับประกันว่าคุณมีและต้องการดังนั้นเสนอให้

หากคุณมีการรับประกันคุณสามารถทำได้ด้วยรหัสสั้น ๆ แนวคิดคือการใช้คุณสมบัติการแทนที่ข้อความทุบตีเพื่อตัดเครื่องหมายทับสุดท้ายและสิ่งต่อไปนี้

ตอบคำถามง่าย ๆ ถึงซับซ้อนกว่าของคำถามต้นฉบับ

หากเส้นทางรับประกันว่าจะจบโดยไม่มีเครื่องหมายทับ (เข้าและออก)

P=/home/smith/Desktop/Test ; echo "${P%/*}"
/home/smith/Desktop

หากพา ธ ถูกรับประกันว่าจะจบด้วยเครื่องหมายสแลชหนึ่งอัน (เข้าและออก)

P=/home/smith/Desktop/Test/ ; echo "${P%/*/}/"
/home/smith/Desktop/

หากเส้นทางอินพุตอาจลงท้ายด้วยศูนย์หรือหนึ่งทับ (ไม่เกิน) และคุณต้องการเส้นทางออกโดยไม่ต้องทับ

for P in \
    /home/smith/Desktop/Test \
    /home/smith/Desktop/Test/
do
    P_ENDNOSLASH="${P%/}" ; echo "${P_ENDNOSLASH%/*}"
done

/home/smith/Desktop
/home/smith/Desktop

หากเส้นทางเข้าอาจมีหลายทับภายนอกและคุณต้องการเส้นทางออกไปสิ้นสุดโดยไม่ต้องทับ

for P in \
    /home/smith/Desktop/Test \
    /home/smith/Desktop/Test/ \
    /home/smith///Desktop////Test// 
do
    P_NODUPSLASH="${P//\/*(\/)/\/}"
    P_ENDNOSLASH="${P_NODUPSLASH%%/}"
    echo "${P_ENDNOSLASH%/*}";   
done

/home/smith/Desktop
/home/smith/Desktop
/home/smith/Desktop

1
คำตอบที่ยอดเยี่ยม เนื่องจากดูเหมือนว่าเขากำลังมองหาวิธีที่จะทำสิ่งนี้จากภายในสคริปต์ (ค้นหาไดเรกทอรีปัจจุบันของสคริปต์และเป็นผู้ปกครองและทำสิ่งที่สัมพันธ์กับที่สคริปต์อาศัยอยู่) นี่เป็นคำตอบที่ยอดเยี่ยมและคุณสามารถควบคุมได้มากขึ้นว่า การป้อนข้อมูลที่มีเครื่องหมายทับต่อท้ายหรือไม่โดยใช้การขยายตัวพารามิเตอร์ต่อต้าน${BASH_SOURCE[0]}ซึ่งจะเป็นเส้นทางที่เต็มไปด้วยสคริปต์ถ้ามันไม่ได้มาผ่านหรือsource .
dragon788

7

ถ้า/home/smith/Desktop/Test/../เป็นสิ่งที่คุณต้องการ:

dirname 'path/to/child/dir'

เท่าที่เห็นนี่


1
ขอโทษฉันหมายถึงเหมือนสคริปต์ทุบตีฉันมีตัวแปรที่มีเส้นทางของไฟล์
YTKColumba

bash: dirname 'Test': syntax error: invalid arithmetic operator (error token is "'Test'")ใช้รหัสที่ทำให้ผมเกิดข้อผิดพลาด รหัสที่เชื่อมโยงนั้นผิด
Michael Hoffman

ลองเรียกใช้dirname Testจากไดเรกทอรีที่Testมีอยู่
Jon Egeland

1

ขึ้นอยู่กับว่าคุณต้องการเส้นทางสัมบูรณ์หรือไม่คุณอาจต้องการใช้ขั้นตอนเพิ่มเติม:

child='/home/smith/Desktop/Test/'
parent=$(dirname "$child")
abs_parent=$(realpath "$parent")

0

ใช้สิ่งนี้: export MYVAR="$(dirname "$(dirname "$(dirname "$(dirname $PWD)")")")"ถ้าคุณต้องการไดเรกทอรีหลักที่ 4

export MYVAR="$(dirname "$(dirname "$(dirname $PWD)")")" ถ้าคุณต้องการไดเรกทอรีหลักที่ 3

export MYVAR="$(dirname "$(dirname $PWD)")" ถ้าคุณต้องการไดเรกทอรีหลักที่ 2


ขอบคุณ! เพียงสิ่งที่ฉันกำลังมองหา
Josue Abarca

0

น่าเกลียด แต่มีประสิทธิภาพ

function Parentdir()

{

local lookFor_ parent_ switch_ i_

lookFor_="$1"

#if it is not a file, we need the grand parent
[ -f "$lookFor_" ] || switch_="/.."

#length of search string
i_="${#lookFor_}"

#remove string one by one until it make sens for the system
while [ "$i_" -ge 0 ] && [ ! -d "${lookFor_:0:$i_}" ];
do
    let i_--
done

#get real path
parent_="$(realpath "${lookFor_:0:$i_}$switch_")" 

#done
echo "
lookFor_: $1
{lookFor_:0:$i_}: ${lookFor_:0:$i_}
realpath {lookFor_:0:$i_}: $(realpath ${lookFor_:0:$i_})
parent_: $parent_ 
"

}

    lookFor_: /home/Om Namah Shivaya
{lookFor_:0:6}: /home/
realpath {lookFor_:0:6}: /home
parent_: /home 


lookFor_: /var/log
{lookFor_:0:8}: /var/log
realpath {lookFor_:0:8}: /UNIONFS/var/log
parent_: /UNIONFS/var 


lookFor_: /var/log/
{lookFor_:0:9}: /var/log/
realpath {lookFor_:0:9}: /UNIONFS/var/log
parent_: /UNIONFS/var 


lookFor_: /tmp//res.log/..
{lookFor_:0:6}: /tmp//
realpath {lookFor_:0:6}: /tmp
parent_: / 


lookFor_: /media/sdc8/../sdc8/Debian_Master//a
{lookFor_:0:35}: /media/sdc8/../sdc8/Debian_Master//
realpath {lookFor_:0:35}: /media/sdc8/Debian_Master
parent_: /media/sdc8 


lookFor_: /media/sdc8//Debian_Master/../Debian_Master/a
{lookFor_:0:44}: /media/sdc8//Debian_Master/../Debian_Master/
realpath {lookFor_:0:44}: /media/sdc8/Debian_Master
parent_: /media/sdc8 


lookFor_: /media/sdc8/Debian_Master/../Debian_Master/For_Debian
{lookFor_:0:53}: /media/sdc8/Debian_Master/../Debian_Master/For_Debian
realpath {lookFor_:0:53}: /media/sdc8/Debian_Master/For_Debian
parent_: /media/sdc8/Debian_Master 


lookFor_: /tmp/../res.log
{lookFor_:0:8}: /tmp/../
realpath {lookFor_:0:8}: /
parent_: /

0

เริ่มต้นจากแนวคิด / ความคิดเห็น Charles Duffy - 17 ธันวาคม '14 ที่ 5:32 ในหัวข้อรับชื่อไดเรกทอรีปัจจุบัน (ไม่มีเส้นทางแบบเต็ม) ในสคริปต์ Bash

#!/bin/bash
#INFO : /programming/1371261/get-current-directory-name-without-full-path-in-a-bash-script
# comment : by Charles Duffy - Dec 17 '14 at 5:32
# at the beginning :



declare -a dirName[]

function getDirNames(){
dirNr="$(  IFS=/ read -r -a dirs <<<"${dirTree}"; printf '%s\n' "$((${#dirs[@]} - 1))"  )"

for(( cnt=0 ; cnt < ${dirNr} ; cnt++))
  do
      dirName[$cnt]="$( IFS=/ read -r -a dirs <<<"$PWD"; printf '%s\n' "${dirs[${#dirs[@]} - $(( $cnt+1))]}"  )"
      #information – feedback
      echo "$cnt :  ${dirName[$cnt]}"
  done
}

dirTree=$PWD;
getDirNames;

0

nth_path=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd ../../../ && pwd)ถ้าด้วยเหตุผลอะไรก็ตามที่คุณมีความสนใจในการนำขึ้นจำนวนเฉพาะของไดเรกทอรีคุณยังสามารถทำ: สิ่งนี้จะให้ไดเรกทอรีผู้ปกครอง 3 รายการ


ทำไม $ {BASH_SOURCE [0]} แทนที่จะเป็น $ BASH_SOURCE ที่ง่ายกว่า
Nam G VU
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.