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
Create new file but add number if filename already exists in bash
提问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 touch
with 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 max
value can be used to guard against infinite loops when the files can't be created for other reason than noclobber
.
max
当文件由于其他原因无法创建时,该值可用于防止无限循环noclobber
。
Note that noclobber
only applies to the >
operator, not >>
nor <>
.
请注意,noclobber
仅适用于>
运算符,不适用于>>
或<>
。
Remaining race condition
剩余比赛条件
Actually, noclobber
does not remove the race condition in all cases. It only prevents clobbering regularfiles (not other types of files, so that cmd > /dev/null
for 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 zsh
shell instead of bash
which has a raw interface to open()
as the sysopen
builtin in the zsh/system
module:
对于完全没有竞争条件的工作版本,您可以使用zsh
shell 而不是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 touch
or 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
- 在管道和增量中组合所有内容(上面的完整表达式)