Linux 创建新文件,但如果文件名已存在于 bash 中,则添加编号

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12187859/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 14:12:47  来源:igfitidea点击:

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

linuxbashfilenames

提问by heltonbiker

I found similar questions but not in Linux/Bash

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

I want my script to create a file with a given name (via user input) but add number at the end if filename already exists.

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

Example:

例子:

$ create somefile
Created "somefile.ext"
$ create somefile
Created "somefile-2.ext"

采纳答案by choroba

The following script can help you. You should not be running several copies of the script at the same time to avoid race condition.

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

name=somefile
if [[ -e $name.ext || -L $name.ext ]] ; then
    i=0
    while [[ -e $name-$i.ext || -L $name-$i.ext ]] ; do
        let i++
    done
    name=$name-$i
fi
touch -- "$name".ext

回答by bta

Try something like this (untested, but you get the idea):

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

filename=

# 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

Depending on what you're doing, replace the calls to touchwith whatever code is needed to create the files that you are working with.

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

回答by Stephane Chazelas

To avoid the race conditions:

为了避免竞争条件

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"\n' "$file"
echo some text in it >&3

And in addition, you have the file open for writing on fd 3.

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

With bash-4.4+, you can make it a function like:

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

create() { # fd base [suffix [max]]]
  local fd="" base="" 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
}

To be used for instance as:

例如用作:

create 3 somefile .ext || exit
printf 'File: "%s"\n' "$REPLY"
echo something >&3
exec 3>&- # close the file

The maxvalue can be used to guard against infinite loops when the files can't be created for other reason than noclobber.

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

Note that noclobberonly applies to the >operator, not >>nor <>.

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

Remaining race condition

剩余比赛条件

Actually, noclobberdoes not remove the race condition in all cases. It only prevents clobbering regularfiles (not other types of files, so that cmd > /dev/nullfor instance doesn't fail) and has a race condition itself in most shells.

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

The shell first does a stat(2)on the file to check if it's a regular file or not (fifo, directory, device...). Only if the file doesn't exist (yet) or is a regular file does 3> "$file"use the O_EXCL flag to guarantee not clobbering the file.

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

So if there's a fifo or device file by that name, it will be used (provided it can be open in write-only), and a regular file may be clobbered if it gets created as a replacement for a fifo/device/directory... in between that stat(2)and open(2)without O_EXCL!

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

Changing the

改变

  { command exec 3> "$file"; } 2> /dev/null

to

  [ ! -e "$file" ] && { command exec 3> "$file"; } 2> /dev/null

Would avoid using an already existing non-regular file, but not address the race condition.

将避免使用已经存在的非常规文件,但不会解决竞争条件。

Now, that's only really a concern in the face of a malicious adversary that would want to make you overwrite an arbitrary file on the file system. It does remove the race condition in the normal case of two instances of the same script running at the same time. So, in that, it's better than approaches that only check for file existence beforehand with [ -e "$file" ].

现在,这只是面对想要让您覆盖文件系统上的任意文件的恶意对手时才真正关心的问题。在同一脚本的两个实例同时运行的正常情况下,它确实消除了竞争条件。因此,在这方面,它比仅使用[ -e "$file" ].

For a working version without race condition at all, you could use the zshshell instead of bashwhich has a raw interface to open()as the sysopenbuiltin in the zsh/systemmodule:

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

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"\n' "$file"
echo some text in it >&3

回答by Oliver

This is a much better method I've used for creating directories incrementally.

这是我用来增量创建目录的更好的方法。

It could be adjusted for filename too.

它也可以针对文件名进行调整。

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\n" $(expr ${LAST_SOLUTION: -4} + 1))
else
    mkdir SOLUTION_0001
fi

回答by Gyuha Shin

Try something like this

尝试这样的事情

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

回答by SamWN

A simple repackaging of choroba's answeras a generalized function:

choroba 的答案简单地重新打包为通用函数:

autoincr() {
    f=""
    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")"

回答by Hugoren Martinako

Easier:

更轻松:

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

You'll get:

你会得到:

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

回答by Serg Stetsuk

Use touchor whatever you want instead of echo:

使用touch或任何你想要的代替echo

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

Parts of expression explained:

部分表达解释:

  • list files by pattern: ls file*
  • take only number part in each line: sed -n 's/file\([0-9]*\)/\1/p'
  • apply reverse human sort: sort -rh
  • take only first line (i.e. max value): head -n 1
  • combine all in pipe and increment (full expression above)
  • 按模式列出文件: ls file*
  • 每行只取数字部分: sed -n 's/file\([0-9]*\)/\1/p'
  • 应用反向人类排序: sort -rh
  • 只取第一行(即最大值): head -n 1
  • 在管道和增量中组合所有内容(上面的完整表达式)