Генерация уникального имени файла в PHP проектах

Папка с файлами

C самых первых проектов, поддерживающих загрузку файлов на сервер, любой программист сталкивается с необходимостью генерации уникальных случайных имён для загруженных файлов. Рассмотрим несколько вариантов решения данной проблемы.

Предположим, мы должны сохранять все файлы в одну папку. Чтобы файлы не повторялись и чтобы не возникало проблем с кириллицей нам лучше давать им уникальные имена вида

f1ga2343h4bc3
537f2ha8b321a
dh420h3aac370

или

f1ga2343h4bc3534fa.jpg
537f2ha8b321adf676.jpg
dh420h3aac370aac09.gif

Самый лёгкий вариант – это использование встроенной в PHP функции uniqid():

$filename = uniqid();

Эта функция вернёт случайную 13-символьную строку. Если же нужно делать имена длиннее, то можно использовать функции вычисления хэша от случайной строки:

$filename = md5(microtime() . rand(0, 9999));

Функция md5() генерирует 32-символьную строку. Можно, конечно использовать любую другую функцю. При желании можно установить любую длину от 1 до 32 обрезав md5-хэш функцией substr():

$filename = substr(md5(microtime() . rand(0, 9999)), 0, 20);

Если нужно хранить файл с расширением, то его расширение можно легко приписывать к идентификатору:

$extension = 'jpg';
$filename = uniqid() . '.' . $extension;

Это простые способы, но у них есть один недостаток: уникальность имени не контролируется, а следовательно имеется вероятность перезаписи старого файла при случайной генерации такого же имени для нового файла. И эта вероятность тем выше, чем больше файлов сохранено.

Чтобы избежать перезаписи нам необходимо проверять папку на отсутствие такого же файла. Напишем функцию, избавленную от этого недостатка:

/**
 * @author ElisDN <mail@elisdn.ru>
 * @link https://elisdn.ru
 */
class DFileHelper
{
    public static function getRandomFileName($path, $extension='')
    {
        $extension = $extension ? '.' . $extension : '';
        $path = $path ? $path . '/' : '';
 
        do {
            $name = md5(microtime() . rand(0, 9999));
            $file = $path . $name . $extension;
        } while (file_exists($file));
 
        return $name;
    }
}

Здесь в постусловии цикла осуществляется проверка на существование файла. Если файл с таким именем уже есть, то генерируется следующее имя. Вместо md5(microtime() . rand(0, 9999)) для генерации идентификатора можно использовать любой вариант из разобранных выше.

Рассмотрим пример загрузки изображения с использованием данной функции:

$path = 'upload/images';
 
// Получаем расширение загруженного файла
$extension = strtolower(substr(strrchr($_FILES['image']['name'], '.'), 1));
 
// Генерируем уникальное имя файла с этим расширением
$filename = DFileHelper::getRandomFileName($path, $extension);
 
// Собираем адрес файла назначения
$target = $path . '/' . $filename . '.' . $extension;
 
// Загружаем 
move_uploaded_file($_FILES['image']['tmp_name'], $target);

В Yii для получения расширения удобно пользоваться объектом $file класса CUploadedFile:

class Post extends CActiveRecord
{
    const IMAGE_PATH = 'upload/images';
 
    protected function beforeSave()
    {
        if (parent::beforeSave())
        {
            if ($file = CUploadedFile::getInstance($this, 'image'))
            {               
                $extension = strtolower($file->extensionName);
                $filename = DFileHelper::getRandomFileName(self::IMAGE_PATH, $extension);
                $basename = $filename . '.' . $extension;
 
                if ($file->saveAs(self::IMAGE_PATH . '/' . $basename))              
                    $model->image = $basename;
            }           
 
            return true;
        } 
        else 
            return false;
    }
}

С помощью функции DFileHelper::getRandomFileName() мы генерируем уникальное имя файла для папки upload/images и используем это имя для загрузки. Теперь файлы никогда не перезапишутся, так как имена никогда не совпадут.

Комментарии

 

Андрей

С микротаймом и мд5, коллизии никогда не будет по 1 простой причине :)
В одной папке есть лимит количества файлов, после которого просто система заглохнет читать папку, а удалять файлы в такой папке будет пыткой даже для СИшных приложений и будет очень дорогостоящей операцией для системы.

Ответить

 

Дмитрий Елисеев

Увы, но это не причина.

Ответить

 

Ra – yabbarov.ru

я делал так:

$DS=DIRECTORY_SEPARATOR;
$date = new DateTime('now');
$path = Yii::app()->basePath.$DS.'..'.$DS.'uploads'.$DS.$date->format('Y').$DS.$date->format('m').$DS.$date->format('d');

if (is_dir($path)===false) {
    if (!mkdir($path,$this->permissions,$recursive=true)) {

Не знаю, правильно, или нет, но так в одной папке много не скопится :)

И такой вопрос: а если надо сохранить имена файлов (ещё и на русском языке)? Сервер корёжит имена при загрузке.

Ответить

 

Дмитрий Елисеев

Да, кстати, лучше создавать папки по дате. Так многие CMS делают (тот же WordPress).

И касательно имён: можно хранить имя в ещё одной ячейке, а файлы отдавать через x-sendfile в Nginx с передачей оригинального имени.

Ответить

 

Валерий – valion.ua

Я делал проще с использованием TIME():

if($_FILES){ 
   $coun = count($_FILES[foto][name]);
   $time=time();
      for($i=0;$i<=$coun;$i++){
      $ext = strtolower(substr(strrchr($_FILES['foto']['name'][$i],'.'), 1));
      $name_img = $time.$i.'.'.$ext;
      $path = $_SERVER[DOCUMENT_ROOT]."/tmp/".$name_img; 
      copy($_FILES['foto']['tmp_name'][$i],$path)
   }
}
Ответить

 

Andrey

Статья вводит бедных юниоров в заблуждение. Для действительного случайного имени нужно использовать tempnam, а не указанный код.

Ответить

 

Дмитрий Елисеев

Увы, эта функция не припишет к имени файла расширение. После конкатенации расширения её также придётся оборачивать в этот же цикл do-while.

Ответить

 

Andrey

И зачем ему расширение?

Очень прошу, поменяйте хотя бы для первых блоков (с uniqid и md5(microtime)) описание, допишите, что этот способ использовать _нельзя_. Ну открывают же и копируют, не глядя.

Ответить

 

Дмитрий Елисеев

А Вы изображения без расширений загружаете?

Ответить

 

Максим Тимофеев

В Yii2 есть методы:

Yii::$app->security->generateRandomString()
Yii::$app->security->generateRandomKey($length)

Для тех кто не любит MD5.

Ответить

 

Andrey

Посмотрел на код нашего юниора, который использует эту замечательную статью, вскрылся очередной косяк:

// Генерируем уникальное имя файла с этим расширением
$filename = DFileHelper::getRandomFileName($path, $extension);

// ВОТ ТУТ ДРУГОЙ ПРОЦЕСС СОЗДАЕТ ФАЙЛ С ТАКИМ ЖЕ ИМЕНЕМ

// Собираем адрес файла назначения
$target = $path . '/' . $filename . '.' . $extension;

// Загружаем
move_uploaded_file($_FILES['image']['tmp_name'], $target);

// ОПА - А ТАКОЙ ФАЙЛ УЖЕ ЕСТЬ


Слушайте, ну уберите статью, ну пожалуйста. Она не имеет абсолютно никакого смысла и только запутывает людей. Пожалуйста, посмотрите в исходниках PHP, как работает tempnam (подсказка: через mkstemp), посмотрите, что такое mkstemp и как она отличается от mktemp. В качестве бонуса попробуйте осознать понятия "атомарность" и "параллельное выполнение".
Ваша статья в гугле висит на первых местах и несёт страдание в этот мир.

Ответить

 

Дмитрий Елисеев

Да запросто. Если подскажете, как сгенерировать случайное имя с расширением – удалю.

Ответить

 

Andrey

Да хотя бы переработайте свой же код, чтобы он не просто пытался сгенерировать случайное имя файла, а пытался _создать_ файл со случайным именем до тех пор, пока у него это не получится, будет значительно лучше.
В простой генерации случайного несуществующего имени практического смысла мало - кроме гарантии, что _на момент_ генерации имени такого файла не существовало, больше никаких гарантий нет. А это достаточно бестолково :)

Ответить

 

Дмитрий Елисеев

Переработал ещё позавчера.

Ответить

 

Виктор – brainforce.kiev.ua

Исправьте вместо

do {
    $name = md5(microtime() . rand(0, 9999));
    $file = $path . $name . $extension;
} while (file_exists($file));

нужно

do {
    $name = md5(microtime() . rand(0, 9999));
    $file = $path . $name . $extension;
} while ( ! file_exists($file));

Иначе с цикла мы выйдем когда найдем файл с таким же именем? что нам как раз не нужно

Ответить

 

Дмитрий Елисеев

Нет. Наоборот. Это цикл while, а не until.

Ответить

 

Виктор – brainforce.kiev.ua

Да, ошибся, сорри

Ответить

 

Игорь

С интересом читаю все ваши статьи, но относительного правильности способа генерации уникального имени файла, который изложен в этом материале, возникают сомнения.
Все, что делает статический метод DFileHelper::getRandomFileName() - это генерирует уникальное (на момент генерации) имя файла в рамках заданной директории.
Однако, представим, что с приложением параллельно работают несколько клиентов (клиент1 и клиент2):
Клиент1 вызывает метод getRandomFileName() - генерируется уникальное имя;
Клиент2 вызывает этот же метод с некоторым запозданием, но еще до вызова Клиентом1 метода, который запишет файл в директорию.

В этой ситуации может произойти такое, что и для Клиента1, и для Клиента2 будет создано одно и тоже "уникальное" имя, но Клиент2 не узнает об этом, поскольку Клиент1 еще не произвел запись файла в каталог, а ведь лишь после этого метод file_exists() сможет определить, что файл с таким именем уже существует в директории.

Считаю, что после генерации уникального имени необходимо сразу же записывать файл в директорию (пустой) и после этого возвращать, действительно уникальное имя клиенту.

Ответить

 

Дмитрий Елисеев

Сейчаc от этого спасает только rand(0, 9999) в имени, что даёт крайне малую верятность этого события:

$name = md5(microtime() . rand(0, 9999));

Можно повысить верхний предел до миллиона, что снизит возможность совпадения до одной миллионной.

Но всё равно создание пустого файла не спасёт, так как это «сразу же» тоже не будет мгновенным.

Ответить

 

Игорь

А разве так не надежнее будет?

            
Ответить

 

alexsandr

чтобы исключить возможность ошибки надо просто содать отдельную дирректорию для файлов пользователя с именем ID

Ответить

 

Сергей

Я пользуюсь алгоритмом генерации файлов как у автора, только не проверяю директорию на наличие такого файла уже, какова вероятность что два файла записанные в разное время будут иметь одинаковый хеш?, плюс ко всему у меня у каждого юзера своя папка с файлами, название которой соответствуют id юзера, который уникальный в системе...

Вопрос, какова вероятность перезаписи файла в моем случаи, я хеш не обрезаю вообще

Ответить

 

Игорь

Значит в Вашем случае способ, предложенный автором, используется просто как генератор имени файла.
Если Вы не проверяете директорию на наличие в ней файла с именем, которое было сгенерировано для записи нового файла - вероятность перезаписи существует, как если бы файлы записывались "в разное время", так и одновременно, т.к. в md5 могут быть коллизии.

Ответить

 

Алексей

Хорошая информация

Ответить

 

RusAlex

Почему не использовать tempnam() ?

Ответить

 

Дмитрий Елисеев

Уже отвечал выше. А именно из-за невозможности работать с расширениями файлов.

Ответить

 

Ra – yabbarov.ru

Может так?

<?php
const DS = DIRECTORY_SEPARATOR;


class FileHelper
{
    /**
     * Creates file with given path and extension, random name (using unigid())
     * @param string $path
     * @param string $ext
     * @return string $name
     * @throws CHttpException if file not created
     */
    public static function getUniqueFileName($path = '', $ext = '')
    {
        $attempts = 238328; // 62 x 62 x 62 - глупо но не знаю как лучше
        $path = $path ? $path . DS : '';
        $ext = $ext ? '.' . $ext : '';

        for( $count = 0; $count < $attempts; ++$count) {
            $filename = uniqid().$ext;
            $fullfilename = $path . DS . $filename;
            if( !($fd = @fopen($fullfilename, "x+")) )
                continue;
            fclose($fd);
            return $filename;
        }

        throw new CHttpException(500,'Could not create file '.$fullfilename);
    }
}


class Article extends CActiveRecord
{
    public $permissions=0755;

    /**
     * Renames uploaded file, creates folder for it, and saves it there
     * @return boolean
     * @throws CHttpException if folder not created
     */
    protected function beforeSave()
    {
        if (parent::beforeSave())
        {
            if ($file = CUploadedFile::getInstance($this, 'image'))
            {
                $date = new DateTime('now');
                //как бы это укоротить :) и тут webroot не прокатывает под виндой
                $path = Yii::getPathOfAlias('application').DS.'..'.DS.'media'.DS.'uploads'.DS.'news'.
                    $date->format('Y').DS.$date->format('m').DS.$date->format('d');

                if (is_dir($path)===false) {
                    if (!mkdir($path,$this->permissions,$recursive=true)) {
                        $ext = strtolower($file->extensionName);
                        $filename = FileHelper::getUniqueFileName($path, $ext);

                        if ($file->saveAs($filename))
                            // здесь тоже захардкожено, исправить
                            $model->image = '/media/uploads/news/'.$date->format('Y').'/'.
                                $date->format('m').'/'.$date->format('d').'/'.$filename;

                    }
                    else
                        throw new CHttpException(500, 'Could not create folder '.$path);
                }
            }
            return true;
        }
        else
            return false;
    }
}
Ответить

 

Дима

Отличная статья! Краткость сестра таланта!

Ответить

 

Никита

Предлагаю использовать такую функцию $filename = md5(microtime() . rand(0, 9999)); Только еще дописывать идентификатор пользователя - он уж точно будет уникальным. Если один и тот же пользователь добавляет одновременно много файлов в цикле просто еще крутить счетчик и будет что то типа $filename = md5(microtime() . rand(0, 9999)) . $_SESSION['userId'].$i; $i++;

Ответить

 

Дмитрий Елисеев

:)

Ответить

 

Влад – infoblog1.ru

Вот тут можно проверить правильность работы скрипта
Генератор

Ответить

 

Виктор Туляков

Приветствую Коллеги!
~~~
Ищу способ задания уникального имени файла, но мне необходимо учесть еще один параметр.
На сайте хранится около 1000-3000 фотографий, фотки на сайт может загружать только один человек (Администратор сайта). В момент загрузки Я понял, задам уникальное имя файла, а что делать если Администратор сайта случайно захочет загрузить фотографию, которую он уже загружал месяц назад?

В связи с этим вот какой вопрос! Можно ли определить уникальное имя загружаемому файлу не по случайному значению, например времени, а по, например, внутренней хеш-сумме изображения? Чтобы при повторной загрузке именно этого изобращения система выдавала сообщение "Такой файл уже существует!".

Ответить

 

Дмитрий Елисеев

Да, можно сгенерировать имя через md5_file:

$file = $path . '/' . md5_file($uploadedFile->tempName) . '.' . $ext;

а потом при наличии такого файла по результату file_exists($file) выдавать это сообщение.

Ответить

 

Виктор Туляков

Благодарю Уважаемый!

Ответить

 

Бауржан АТинов

спасибо, работает

Ответить

 

Evgeny

На 2х разных проектах нашел одинаковый код, написанный разными разработчиками, удивился, погуглил, оказывается они содрали этот треш отсюда. Пожалуйста не пишите больше, джуниоры еще глупые - тянут всякую каку в руки.

Ответить

 

Дмитрий Елисеев

Напишите в комментарии правильное решение. Добавлю в статью.

Ответить

 

Максим Тимофеев

Можно на Ваше элегантное решение глянуть?

Ответить

 

thou

я просто добавляю в конце имени файла цифру: _1, _2, _3 ...

$uploaddir = '../files/';
$uploadfile = $uploaddir. $name. "." . $type;

$i = 0;
if (file_exists($uploadfile)) {
    while(file_exists($uploaddir. $name. '_'. $i. '.' . $type)) {
        ++$i;
    }
    $uploadfile = $uploaddir. $name. '_'. $i. '.'. $type;
} 
Ответить

 

Алексей

Здравствуйте.
Подскажите пожалуйста, зачем генерировать имя файла использую хеш, кроме причины, чтобы файл не совпал с уже загруженным? Есть ли другие причины?

Ответить

 

Дмитрий Елисеев

Чтобы не было мучений с кириллицей в имени файла, например.

Ответить

 

Максим Тимофеев

название получается правильным несмотря ни на что. Есть файлы с точками в имени. Есть с кириллицей. Это не единственный метод, но самый простой.

Ответить

 

Aleks

md5_file() Вам в помощь! )

Ответить

 

Дмитрий Елисеев

Два пользователя загружают одинаковый файл... и второй пользователь свой файл удаляет. И у первого файл тоже пропадает.

Ответить

 

марат – icomans.work

я сделал так (файловый обменник) :

// Максимально допустимый размер загружаемого файла - 500Мб
$MaxFileSizeInBytes = 5242880000;
// Разрешение расширения файлов для загрузки
$AllowFileExtension = array('jpg', 'png', 'jpeg', 'gif', 'rar', 'zip', 'doc', 'pdf', 'djvu','ico','exe','pkg','app');
// Оригинальное название файла
$FileName = $_FILES['uploaded_file']['name'];
// Полный путь до временного файла
$TempName = $_FILES['uploaded_file']['tmp_name'];
// Папка где будут загружатся файлы
$UploadDir = "uploads/";
// Полный путь к новому файлу в папке сервера
$NewFilePatch = $UploadDir.uniqid();;
if($FileName) {
    // Проверка если расширение файла находится в массиве доступных
    $FileExtension = pathinfo($FileName, PATHINFO_EXTENSION);
    if(!in_array($FileExtension, $AllowFileExtension)) {
        echo "Файлы с расширением {$FileExtension} не допускаются";
    }
     else {
         // Проверка размера файла
         if(filesize($TempName) > $MaxFileSizeInBytes) {
             echo "Размер загружаемого файла превышает 5МБ";
         }
          else {
              // Проверяем права доступа на папку
              if(!is_writable($UploadDir)) {
                  echo "Папка ".$UploadDir." не имеет прав на запись";
              }
               else {
                   // Копируем содержимое временного файла $TempName и создаем нового в папке сервера
                   $CopyFile = copy($TempName, $NewFilePatch);
                   if(!$CopyFile) {
                       echo "Возникла ошибка, файл не удалось загрузить!";
                   }
                    else {
                        echo "Файл успешно загружен!<br>Ссылка на файл: <a href='{$NewFilePatch}'>{$NewFilePatch}</a>";
                    }
               }
          }
     }
}
Ответить

 

Сашка

Добрый день Дмитрий!
У Вас в коде:
strtolower(substr(strrchr($_FILES['image']['name'], '.'), 1));

- А почему не используете pathinfo?
pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION)

Спасибо.
С ув. Ваш периодически-постоянный читатель!

Ответить

 

Дмитрий Елисеев

Три года назад мне было всё равно :)

Ответить

 

Сашка

- Ну поправьте может)
Все таки репутация же у Вас то есть сейчас!

Ответить

 

Максим Тимофеев

Статья обсуждаема, а значит нужна. В каком бы виде она не была. Иногда достаточно натолкнуть на мысль, а не дать готовое решение.

Ответить

 

Денис

Кодеры кодеры

а для обычного человека есть код или плагин под ворпресс чтобы не вникать в смыслы создания вселенной?

нужен плгин, или код для functioins php

с учетом что фотки загружаются сюда

uploads/images

с учетом что стоит галочка, Organise my uploads into month- and year-based folders

Спасибо!

Ответить

 

Денис

сам нашел, пригодится кому-нибудь кто не знает английский например

function wp_modify_uploaded_file_names($file) {
    $info = pathinfo($file['name']);
    $ext  = empty($info['extension']) ? '' : '.' . $info['extension'];
    $name = basename($file['name'], $ext);

    //$file['name'] = uniqid() . $ext; // uniqid method
    $file['name'] = md5($name) . $ext; // md5 method
    // $file['name'] = base64_encode($name) . $ext; // base64 method

    return $file;
}

add_filter('wp_handle_upload_prefilter', 'wp_modify_uploaded_file_names', 1, 1);
Ответить

 

Павел

Денис, в коде две строки лишние, ибо //. И может быть такой код будет удобнее для WP пользователей:

function wp_modify_uploaded_file_names($file) {
    $info = pathinfo($file['name']);
    $ext  = empty($info['extension']) ? '' : '.' . $info['extension'];
    $name = basename($file['name'], $ext);
    $usrnm = get_current_user_id();

    $file['name'] = base_convert(uniqid($usrnm, true),16,36) . $ext;

    return $file;
}

Используем uniqid с префиксом на основе ID пользователя и добавляем "энтропию".

Ответить

 

Алексей – irogex.ru

А мне так функция на 5 с +++, добавил свои плюшки, вообщем спасибо..

$name_img='perfix_'.$file_name.(self::getRandomFileName($path, $file_type)).'_dev_irogex.ru_.'.$file_type;
Ответить

 

Михаил

Доброго времени!
Мне нужна строковая функция чтобы в строке:
1. пробел(ов) заменить одним знаком "-"
2. изменить регистр в нижний,
3. удалить все знаки кроме букв и цифр.
4. и заменить все буквы на английский
Если можете помогите кто нибудь пожалуйста.

Ответить

 

Дмитрий Елисеев

При наличии php_intl:

function slugify($string) {
    $string = transliterator_transliterate("Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove; Lower();", $string);
    $string = preg_replace('/[-\s]+/', '-', $string);
    return trim($string, '-');
}

Иначе вручную по вашим действиям:

function slugify($string) {
    $string = preg_replace('#\s+#is', '-', $string);
    $string = mb_strtolower($string, 'utf-8');
    $string = preg_replace('#[^\w_]+#uis', '', $string);
    $string = strtr($string, [
        'а' => 'a',
        'б' => 'b',
        ...
    ]);
    return trim($string, '-');
}
Ответить

 

Михаил

Спасибо за поддержку!
Я понятие не имею что значит При наличии php_intl:
Наверное мне подойдет 2-ой вариант.
Нужно чтобы переформатированный текст стал названием изображении.
Попробую, о результате сообщу вам.
Спасибо еще раз.

Ответить

 

Михаил

Пробовал добавить функцию

function slugify($string) {
    $string = preg_replace('#\s+#is', '-', $string);
    $string = mb_strtolower($string, 'utf-8');
    $string = preg_replace('#[^\w_]+#uis', '', $string);
    $string = strtr($string, [
        'а' => 'a',
        'б' => 'b',
        ...
    ]);
    return trim($string, '-');
}

выдал ошибку:

syntax error, unexpected '[' in Z:\home

Так как у меня отсутствует PHP знание, я не в силе исправиться с этими ошибками.
Посмотрите пожалуйста может что не дописали.

Ответить

 

Дмитрий Елисеев

У вас PHP ранней версии. Используйте array(...) для массивов вместо [...]:

$string = strtr($string, array(
    'а' => 'a',
    'б' => 'b',
        ...
));
Ответить

 

Михаил

Спасибо большое, попробую.

Ответить

 

Алексей

Если достаточно латинизировать, а не транскрибировать на английский, тогда проще:

$string = strtr($string, "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя", "ABVGDEEGZIJKLMNOPRSTUFHCCSSJYJEUAabvgdeegzijklmnoprstufhccssjyjeua");
Ответить

 

Саня

А у меня вопрос такой:
$filename = DFileHelper::getRandomFileName(self::IMAGE_PATH, $extension);

Мне рандом не упал, могу ли я просто вписать имя файла?
То, есть у меня в папке только один должен хранится и заменятся

Ответить

 

PHPDevil – webwizardry.ru

А файл вы грузите к чему? В поле модели?
Так пофиг как называется - пихаем название файла в поле, а самому имени файла приписываем id этой же записи... ну и как бы решена проблема

Ответить

 

Дмитрий Елисеев

Если id автоинкрементный, то он неизвестен перед сохранением.

Ответить

 

PHPDevil – webwizardry.ru

Если это форма для юзера - то исходя из что-там происходит обрабатываем фал сразу из temp

Ответить

 

Arman

Ох как не люблю такие условия для циклов =) Какие гарантии что не уйдет в цикл завтра? Допустим джуниор поменяет реализацию генерации и на деле выйдет несколько возможных вариантов имени. Всегда стараюсь делать через for с конечным количеством итераций, а дальше кидаю исключение.

Ответить

Оставить комментарий

Войти | Завести аккаунт | Войти через


(никто не увидит)





Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>