ฉันจะลบนามสกุลไฟล์ในเชลล์สคริปต์ได้อย่างไร


145

มีอะไรผิดปกติกับรหัสต่อไปนี้?

name='$filename | cut -f1 -d'.''

ตามที่ฉันได้รับสายตัวอักษร$filename | cut -f1 -d'.'แต่ถ้าฉันลบอัญประกาศฉันไม่ได้รับอะไร ในขณะเดียวกันพิมพ์

"test.exe" | cut -f1 -d'.'

ในเชลล์ให้ผลลัพธ์ที่ฉันต้องการ, test. ฉันรู้แล้วว่า$filenameได้รับการกำหนดค่าที่เหมาะสม สิ่งที่ฉันต้องการจะทำคือกำหนดตัวแปรชื่อไฟล์ที่ไม่มีนามสกุล


6
basename $filename .exeจะทำในสิ่งเดียวกัน นั่นคือสมมติว่าคุณรู้เสมอว่าคุณต้องการลบส่วนขยายใด
mpe

6
@mpe basename "$filename" .exeคุณหมายถึง มิฉะนั้นชื่อไฟล์ที่มีช่องว่างจะเป็นข่าวร้าย
Charles Duffy

คำตอบ:


113

คุณควรใช้ไวยากรณ์การแทนที่คำสั่ง$(command)เมื่อคุณต้องการรันคำสั่งในสคริปต์ / คำสั่ง

ดังนั้นสายของคุณจะเป็น

name=$(echo "$filename" | cut -f 1 -d '.')

คำอธิบายรหัส:

  1. echoรับค่าของตัวแปร$filenameและส่งไปยังเอาต์พุตมาตรฐาน
  2. จากนั้นเราก็คว้าเอาท์พุทและท่อไปที่ cutคำสั่ง
  3. cutจะใช้ เป็นตัวคั่น (หรือที่เรียกว่าตัวคั่น) สำหรับการตัดสตริงเป็นเซ็กเมนต์และโดย-fเราเลือกเซ็กเมนต์ที่เราต้องการให้มีในเอาต์พุต
  4. จากนั้นการ$()ทดแทนคำสั่งจะได้รับผลลัพธ์และคืนค่าของมัน
  5. ค่าที่ส่งคืนจะถูกกำหนดให้กับตัวแปรที่ชื่อว่า name

โปรดทราบว่านี่จะให้ส่วนของตัวแปรจนถึงช่วงแรก.:

$ filename=hello.world
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello.hello.hello
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello
$ echo "$filename" | cut -f 1 -d '.'
hello

ขอบคุณ ฉันยังสังเกตเห็นว่าฉันต้องใช้คำสั่ง echo name =echo $filename | cut -f1 -d'.'
mimicocotopus

17
Backticks จะถูกคัดค้านโดย POSIX $()เป็นที่ต้องการ
จอร์แดน

3
การแยกและท่อเพื่อให้ได้อักขระน้อยนั้นเป็นวิธีแก้ปัญหาที่เลวร้ายที่สุดเท่าที่จะเป็นไปได้
Jens

39
ปัญหาเกี่ยวกับคำตอบนี้คือสมมติว่าสตริงอินพุตมีเพียงหนึ่งจุด ... @chepner ด้านล่างมีทางออกที่ดีกว่ามาก ... name = $ {filename%. *}
Scott Stensland

4
คำตอบนี้เป็นลักษณะของผู้เริ่มต้นและไม่ควรแพร่กระจาย ใช้กลไก builtin ตามที่อธิบายโดยคำตอบของ
chepner

259

คุณยังสามารถใช้การขยายพารามิเตอร์:

$ filename=foo.txt
$ echo "${filename%.*}"
foo

6
นี่คือคำอธิบายของคำสั่ง: gnu.org/software/bash/manual/html_node/…
Carlos Robles

1
echo -n "This.File.Has.Periods.In.It.txt" | awk -F. '{$NF=""; print $0}' | tr ' ' '.' | rev | cut -c 2- | revและที่นี่ฉันเป็นเรื่องเกี่ยวกับการใช้งาน ขอบคุณ
user208145

1
สิ่งนี้ใช้ได้กับไฟล์ที่มีนามสกุลหลาย ๆ อันimage.png.gzหรือไม่
Hawker65

11
%.*จะลบเฉพาะส่วนขยายสุดท้ายเท่านั้น ถ้าคุณต้องการที่จะลบทุก%%.*ส่วนขยายการใช้
chepner

1
ดูเหมือนว่าวิธีนี้จะทำงานได้ดีกว่าคำตอบที่ยอมรับ มันจะตัดเฉพาะส่วนขยายสุดท้ายเท่านั้นหากไฟล์มีจุดมากกว่าหนึ่งจุดในขณะที่การตัดจะลบทุกอย่างหลังจากจุดแรก
เดลเดอร์สัน

117

หากคุณรู้จักส่วนขยายคุณสามารถใช้basename

$ basename /home/jsmith/base.wiki .wiki
base

ฉันจะลบออก.wikiและจบด้วยได้/home/jsmith/baseอย่างไร
Gabriel Staples

ปรับปรุง: คำตอบ: ดูstackoverflow.com/a/32584935/4561887 ทำงานได้อย่างสมบูรณ์แบบ!
Gabriel Staples

20

หากชื่อไฟล์ของคุณมีจุด (นอกเหนือจากนามสกุล) ให้ใช้สิ่งนี้:

echo $filename | rev | cut -f 2- -d '.' | rev

1
ฉันลืมการหมุนรอบกลาง แต่เมื่อฉันเห็นมันนี่ยอดเยี่ยมมาก!
สุดยอด Pooba

มันจะดียิ่งขึ้นเมื่อมี-sตัวเลือกที่กำหนดไว้เพื่อcutให้ส่งคืนสตริงว่างเมื่อใดก็ตามที่ชื่อไฟล์ไม่มีจุด
Hibou57

1
นี่ควรเป็นคำตอบที่ได้รับการยอมรับในความคิดของฉันเพราะมันทำงานบนเส้นทางที่มีจุดอยู่ในนั้นด้วยไฟล์ที่ซ่อนเริ่มต้นด้วยจุดหรือแม้กระทั่งกับไฟล์ที่มีนามสกุลหลาย ๆ ตัว
Tim Krief

20
file1=/tmp/main.one.two.sh
t=$(basename "$file1")                        # output is main.one.two.sh
name=$(echo "$file1" | sed -e 's/\.[^.]*$//') # output is /tmp/main.one.two
name=$(echo "$t" | sed -e 's/\.[^.]*$//')     # output is main.one.two

ใช้อะไรก็ได้ที่คุณต้องการ ที่นี่ฉันคิดว่าสุดท้าย.(จุด) ตามด้วยข้อความเป็นส่วนขยาย


6
#!/bin/bash
file=/tmp/foo.bar.gz
echo $file ${file%.*}

เอาท์พุท:

/tmp/foo.bar.gz /tmp/foo.bar

โปรดทราบว่าเฉพาะส่วนขยายสุดท้ายเท่านั้นที่จะถูกลบ


3

basenameคำแนะนำของฉันคือการใช้งาน
มันเป็นค่าเริ่มต้นใน Ubuntu, รหัสง่าย ๆ และจัดการกับกรณีส่วนใหญ่

ต่อไปนี้เป็นกรณีย่อยบางส่วนที่จะจัดการกับช่องว่างและหลายจุด / ส่วนขยายย่อย:

pathfile="../space fld/space -file.tar.gz"
echo ${pathfile//+(*\/|.*)}

มันมักจะกำจัดการขยายจากครั้งแรก.แต่ล้มเหลวใน..เส้นทางของเรา

echo **"$(basename "${pathfile%.*}")"**  
space -file.tar     # I believe we needed exatly that

นี่คือหมายเหตุสำคัญ:

ฉันใช้เครื่องหมายคำพูดคู่ภายในเครื่องหมายคำพูดคู่เพื่อจัดการกับช่องว่าง ใบเสนอราคาเดียวจะไม่ผ่านเนื่องจากการส่งข้อความ $ Bash ผิดปกติและอ่านเครื่องหมายคำพูด "ที่สอง" เป็นครั้งแรก "เนื่องจากการขยายตัว

อย่างไรก็ตามคุณยังคงต้องคิด .hidden_files

hidden="~/.bashrc"
echo "$(basename "${hidden%.*}")"  # will produce "~" !!!  

ไม่ใช่ผลลัพธ์ที่คาดหวัง "" ที่จะทำให้มันเกิดขึ้นใช้$HOMEหรือ/home/user_path/
เพราะทุบตีอีกครั้งคือ "ผิดปกติ" และไม่ขยาย "~" (ค้นหาทุบตี BashPitfalls)

hidden2="$HOME/.bashrc" ;  echo '$(basename "${pathfile%.*}")'

1
#!/bin/bash
filename=program.c
name=$(basename "$filename" .c)
echo "$name"

เอาท์พุท:

program

สิ่งนี้แตกต่างจากคำตอบที่ได้รับจาก Steven Penny เมื่อ 3 ปีที่แล้ว?
gniourf_gniourf

1

ตามที่ Hawker65 ระบุไว้ในความคิดเห็นของคำตอบของ chepner โซลูชันที่ได้รับการโหวตมากที่สุดไม่ได้ดูแลส่วนขยายหลายรายการ (เช่น filename.tar.gz) หรือจุดในส่วนที่เหลือของเส้นทาง (เช่น this.path / with .dots / in.path.name) ทางออกที่เป็นไปได้คือ:

a=this.path/with.dots/in.path.name/filename.tar.gz
echo $(dirname $a)/$(basename $a | cut -d. -f1)

อันนี้ตัดแถบ "tar.gz" โดยการเลือกตัวอักษรก่อนอินสแตนซ์แรกของจุดในชื่อไฟล์ที่ไม่นับเส้นทาง อาจไม่ต้องการตัดส่วนขยายในแบบนั้น
Frotz

0

ปัญหาสองประการเกี่ยวกับรหัสของคุณ:

  1. คุณใช้ '(ติ๊ก) แทน `(ติ๊กด้านหลัง) เพื่อล้อมรอบคำสั่งที่สร้างสตริงที่คุณต้องการเก็บไว้ในตัวแปร
  2. คุณไม่ได้ "echo" ตัวแปร "$ filename" ไปยังไปป์ในคำสั่ง "cut"

ฉันจะเปลี่ยนรหัสของคุณเป็น "name =` echo $ filename | cut -f 1 -d '.' `" ดังที่แสดงด้านล่าง (สังเกตอีกทีว่าเครื่องหมายขีดด้านหลังล้อมรอบนิยามตัวแปรชื่อ):

$> filename=foo.txt
$> echo $filename
foo.txt
$> name=`echo $filename | cut -f1 -d'.'`
$> echo $name
foo
$> 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.