在Bash中提取文件名和扩展名

在Bash中提取文件名和扩展名

技术背景

在Bash脚本编程中,经常需要对文件路径进行处理,提取文件名和扩展名是常见的操作。不同的文件路径格式和文件名特点(如多个扩展名、无扩展名、带特殊字符等)给提取操作带来了一定的复杂性。因此,掌握多种提取文件名和扩展名的方法是很有必要的。

实现步骤

基本方法

  • 首先,使用basename命令获取不包含路径的文件名:
1
2
3
filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"
  • 另一种方式是关注路径中的最后一个/
1
filename="${fullfile##*/}"

使用参数扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 示例文件路径
FILE="example.tar.gz"

# 提取文件名(去除所有扩展名)
echo "${FILE%%.*}" # 输出: example

# 提取文件名(去除最后一个扩展名)
echo "${FILE%.*}" # 输出: example.tar

# 提取扩展名(从第一个点开始)
echo "${FILE#*.}" # 输出: tar.gz

# 提取最后一个扩展名
echo "${FILE##*.}" # 输出: gz

自定义函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
splitPath() {
local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
# 简单的参数验证
(( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
# 提取目录名(父路径)和基本名(文件名)
_sp_dirname=$(dirname "$1")
_sp_basename=$(basename "$1")
# 确定后缀(如果有)
_sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
# 确定基本名根(无后缀的文件名)
if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # 文件名是否以 '.' 开头?
_sp_basename_root=$_sp_basename
_sp_suffix=''
else # 从文件名中去除后缀
_sp_basename_root=${_sp_basename%$_sp_suffix}
fi
# 赋值给输出变量
[[ -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

使用sed命令

1
2
3
4
5
FILE="a.b.js"
NAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
echo $NAME # 输出: a.b
echo $EXTENSION # 输出: js

使用awk命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
f='/path/to/complex/file.1.0.1.tar.gz'

# 提取文件名
echo "$f" | awk -F'/' '{print $NF}'

# 提取最后一个扩展名
echo "$f" | awk -F'[.]' 'NF>1 {print $NF}'

# 提取所有扩展名
echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'

# 提取倒数第二个扩展名
echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'

# 提取基本名
echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'

# 提取扩展后的基本名
echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'

# 提取路径
echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
# 或者
echo "$f" | grep -Eo '.*[/]'

# 提取包含文件的文件夹名
echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'

# 提取版本号
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'

# 提取主版本号
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1

# 提取次版本号
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2

# 提取补丁版本号
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3

# 提取所有组件
echo "$f" | awk -F'[/.]' '{$1=""; print $0}'

# 判断是否为绝对路径
echo "$f" | grep -q '^[/]\|^~/'

核心代码

以下是一个综合的示例代码,展示了多种提取文件名和扩展名的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash

# 示例文件路径
fullfile="/path/to/file.tar.gz"

# 方法1:基本方法
filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"
echo "方法1: 文件名 - $filename, 扩展名 - $extension"

# 方法2:参数扩展
FILE="$fullfile"
echo "方法2: 文件名(去除所有扩展名) - ${FILE%%.*}"
echo "方法2: 文件名(去除最后一个扩展名) - ${FILE%.*}"
echo "方法2: 扩展名(从第一个点开始) - ${FILE#*.}"
echo "方法2: 最后一个扩展名 - ${FILE##*.}"

# 方法3:自定义函数
splitPath() {
local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
(( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
_sp_dirname=$(dirname "$1")
_sp_basename=$(basename "$1")
_sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
if [[ "$_sp_basename" == "$_sp_suffix" ]]; then
_sp_basename_root=$_sp_basename
_sp_suffix=''
else
_sp_basename_root=${_sp_basename%$_sp_suffix}
fi
[[ -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
}

parentpath= fname= fnameroot= suffix=
splitPath "$fullfile" parentpath fname fnameroot suffix
echo "方法3: 父路径 - $parentpath, 文件名 - $fname, 无后缀文件名 - $fnameroot, 后缀 - $suffix"

# 方法4:sed命令
FILE="$fullfile"
NAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
echo "方法4: 文件名 - $NAME, 扩展名 - $EXTENSION"

# 方法5:awk命令
f="$fullfile"
filename=$(echo "$f" | awk -F'/' '{print $NF}')
extension=$(echo "$f" | awk -F'[.]' 'NF>1 {print $NF}')
echo "方法5: 文件名 - $filename, 扩展名 - $extension"

最佳实践

  • 使用自定义函数:对于复杂的文件名和路径处理,使用自定义函数可以提高代码的可读性和可维护性。
  • 考虑边界情况:在处理文件名和扩展名时,要考虑到各种边界情况,如无扩展名的文件名、以.开头的文件名等。
  • 结合多种方法:根据不同的需求,结合使用参数扩展、sedawk等方法,可以更灵活地处理文件路径。

常见问题

  • 文件名无扩展名:使用extension="${filename##*.}"时,如果文件名无扩展名,会返回文件名本身。可以使用自定义函数来处理这种情况。
  • 文件名以.开头filename="${filename%.*}"在文件名以.开头且无其他.时,会返回空字符串。同样,可以使用自定义函数来避免这种问题。
  • 多个扩展名:对于有多个扩展名的文件,如file.tar.gz,要根据具体需求选择合适的提取方法。例如,使用fileExt=${fullfile#*.}; fileName=${fullfile%*.$fileExt}可以提取完整的扩展名。

在Bash中提取文件名和扩展名
https://119291.xyz/posts/2025-05-12.extract-filename-and-extension-in-bash/
作者
ww
发布于
2025年5月12日
许可协议