关于linux:创建新文件但是如果bash中已经存在filename,则添加数字

Create new file but add number if filename already exists in bash

我发现了类似的问题,但在Linux / Bash中没有

我希望我的脚本创建一个具有给定名称的文件(通过用户输入),但如果filename已经存在,则在末尾添加数字。

例:

1
2
3
4
$ create somefile
Created"somefile.ext"
$ create somefile
Created"somefile-2.ext"

以下脚本可以帮助您。您不应该同时运行脚本的多个副本以避免竞争条件。

1
2
3
4
5
6
7
8
9
name=somefile
if [[ -e $name.ext ]] ; then
    i=0
    while [[ -e $name-$i.ext ]] ; do
        let i++
    done
    name=$name-$i
fi
touch"$name".ext


更轻松:

1
touch file`ls file* | wc -l`.ext

你会得到:

1
2
$ ls file*
file0.ext  file1.ext  file2.ext  file3.ext  file4.ext  file5.ext  file6.ext


为避免竞争条件

1
2
3
4
5
6
7
8
9
10
11
12
13
name=some-file

n=
set -o noclobber
until
  file=$name${n:+-$n}.ext
  { command exec 3>"$file"; } 2> /dev/null
do
  ((n++))
done
printf 'File is"%s"
'
"$file"
echo some text in it >&3

此外,您还可以在fd 3上打开文件。

使用bash-4.4+,您可以使其成为如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create() { # fd base [suffix [max]]]
  local fd="$1" base="$2" suffix="${3-}" max="${4-}"
  local n= file
  local - # ash-style local scoping of options in 4.4+
  set -o noclobber
  REPLY=
  until
    file=$base${n:+-$n}$suffix
    eval 'command exec '"$fd"'>"$file"' 2> /dev/null
  do
    ((n++))
    ((max > 0 && n > max)) && return 1
  done
  REPLY=$file
}

例如用作:

1
2
3
4
create 3 somefile .ext || exit
printf 'File:"%s"
'
"$REPLY"
echo something >&3

当由于noclobber之外的其他原因无法创建文件时,max值可用于防止无限循环。

请注意,noclobber仅适用于>运算符,不适用于>><>

剩余的竞争条件

实际上,noclobber并未在所有情况下消除竞争条件。它只能防止破坏常规文件(不是其他类型的文件,因此cmd > /dev/null例如不会失败)并且在大多数shell中都有竞争条件。

shell首先对文件执行stat(2)以检查它是否是常规文件(fifo,目录,设备......)。仅当文件不存在(尚未)或是常规文件时3>"$file"才使用O_EXCL标志来保证不破坏文件。

因此,如果有一个名称的fifo或设备文件,它将被使用(前提是它可以以只写方式打开),如果创建一个常规文件作为fifo / device /目录的替代品,它可能会被破坏。 ...在没有O_EXCL的stat(2)open(2)之间!

现在,面对恶意攻击者而言,这只是一个问题,它会让你覆盖文件系统上的任意文件。它确实消除了同时运行同一脚本的两个实例的正常情况下的竞争条件。所以,在那里,它比预先用[ -e"$file" ]检查文件存在的方法更好。

对于没有竞争条件的工作版本,您可以使用zsh shell而不是bash,它具有open()的原始接口作为zsh/system模块中内置的sysopen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
zmodload zsh/system

name=some-file

n=
until
  file=$name${n:+-$n}.ext
  sysopen -w -o excl -u 3 --"$file" 2> /dev/null
do
  ((n++))
done
printf 'File is"%s"
'
"$file"
echo some text in it >&3

尝试这样的事情

1
2
3
4
5
6
7
8
9
10
11
12
13
name=somefile
path=$(dirname"$name")
filename=$(basename"$name")
extension="${filename##*.}"
filename="${filename%.*}"
if [[ -e $path/$filename.$extension ]] ; then
    i=2
    while [[ -e $path/$filename-$i.$extension ]] ; do
        let i++
    done
    filename=$filename-$i
fi
target=$path/$filename.$extension


使用touch或任何你想要的而不是echo

1
echo file$((`ls file* | sed -n 's/file\([0-9]*\)/\1/p' | sort -rh | head -n 1`+1))

部分表达解释:

  • 按模式列出文件:ls file*
  • 在每一行中仅取数字部分:sed -n 's/file\([0-9]*\)/\1/p'
  • 应用反向人类排序:sort -rh
  • 仅取第一行(即最大值):head -n 1
  • 将所有管道和增量组合在一起(上面的完整表达)


尝试这样的事情(未经测试,但你明白了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
filename=$1

# If file doesn't exist, create it
if [[ ! -f $filename ]]; then
    touch $filename
    echo"Created "$filename""
    exit 0
fi

# If file already exists, find a similar filename that is not yet taken
digit=1
while true; do
    temp_name=$filename-$digit
    if [[ ! -f $temp_name ]]; then
        touch $temp_name
        echo"Created "$temp_name""
        exit 0
    fi
    digit=$(($digit + 1))
done

根据您正在执行的操作,将touch的调用替换为创建您正在使用的文件所需的任何代码。


这是我用于逐步创建目录的更好的方法。

它也可以调整文件名。

1
2
3
4
5
6
7
LAST_SOLUTION=$(echo $(ls -d SOLUTION_[[:digit:]][[:digit:]][[:digit:]][[:digit:]] 2> /dev/null) | awk '{ print $(NF) }')
if [ -n"$LAST_SOLUTION" ] ; then
    mkdir SOLUTION_$(printf"%04d
"
$(expr ${LAST_SOLUTION: -4} + 1))
else
    mkdir SOLUTION_0001
fi


将choroba答案的简单重新包装作为一种通用功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
autoincr() {
    f="$1"
    ext=""

    # Extract the file extension (if any), with preceeding '.'
    [["$f" == *.* ]] && ext=".${f##*.}"

    if [[ -e"$f" ]] ; then
        i=1
        f="${f%.*}";

        while [[ -e"${f}_${i}${ext}" ]]; do
            let i++
        done

        f="${f}_${i}${ext}"
    fi
    echo"$f"
}

touch"$(autoincr"somefile.ext")"