38

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

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

例子:

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

9 回答 9

41

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

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
于 2012-08-29T23:33:44.327 回答
14

更轻松:

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

你会得到:

$ ls file*
file0.ext  file1.ext  file2.ext  file3.ext  file4.ext  file5.ext  file6.ext
于 2018-04-05T11:33:00.260 回答
9

为了避免竞争条件

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

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

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

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
}

例如用作:

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

max文件由于noclobber.

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

剩余比赛条件

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

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

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

改变

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

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

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

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

对于完全没有竞争条件的工作版本,您可以使用zshshell 而不是bash具有原始接口的 shell 作为模块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
于 2012-08-30T10:05:47.207 回答
6

尝试这样的事情

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
于 2016-02-15T06:48:02.557 回答
5

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

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
  • 在管道和增量中组合所有(上面的完整表达式)
于 2018-05-10T07:32:51.263 回答
2

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

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为创建您正在使用的文件所需的任何代码。

于 2012-08-29T23:33:32.320 回答
1

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

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

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
于 2014-02-27T01:45:36.703 回答
0

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

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")"
于 2016-10-14T17:39:42.177 回答
0

没有循环并且不使用正则表达式或shell expr。

last=$(ls $1* | tail -n1)
last_wo_ext=$($last | basename $last .ext)
n=$(echo $last_wo_ext | rev | cut -d - -f 1 | rev)
if [ x$n = x ]; then
    n=2
else
    n=$((n + 1))
fi
echo $1-$n.ext

更简单,没有扩展名和“-1”例外。

n=$(ls $1* | tail -n1 | rev | cut -d - -f 1 | rev)
n=$((n + 1))
echo $1-$n.ext
于 2020-12-27T21:45:14.467 回答