Параметры main. Передача аргументов в программу

15.04.2019

При создании консольного приложения в языке программирования С++, автоматически создается строка очень похожая на эту:

Int main(int argc, char* argv) // параметры функции main()

Эта строка — заголовок главной функции main() , в скобочках объявлены параметры argс и argv. Так вот, если программу запускать через командную строку, то существует возможность передать какую-либо информацию этой программе, для этого и существуют параметры argc и argv . Параметр argc имеет тип данных int , и содержит количество параметров, передаваемых в функцию main . Причем argc всегда не меньше 1, даже когда мы не передаем никакой информации, так как первым параметром считается имя функции. Параметр argv это массив указателей на строки. Через командную строку можно передать только данные строкового типа. Указатели и строки — это две большие темы, под которые созданы отдельные разделы. Так вот именно через параметр argv и передается какая-либо информация. Разработаем программу, которую будем запускать через командную строку Windows, и передавать ей некоторую информацию.

// argc_argv.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include using namespace std; int main(int argc, char* argv) { if (argc > << argv<

// код Code::Blocks

// код Dev-C++

// argc_argv.cpp: определяет точку входа для консольного приложения. #include using namespace std; int main(int argc, char* argv) { if (argc > 1)// если передаем аргументы, то argc будет больше 1(в зависимости от кол-ва аргументов) { cout << argv<

После того как отладили программу, открываем командную строку Windows и перетаскиваем в окно командной строки экзэшник нашей программы, в командной строке отобразится полный путь к программе(но можно прописать путь к программе в ручную), после этого можно нажимать ENTER и программа запустится (см. Рисунок 1).

Рисунок 1 — Параметры функции main

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

Рисунок 2 — Параметры функции main

Аргументом является слово Open , как видно из рисунка, это слово появилось на экране. Передавать можно несколько параметров сразу, отделяя их между собой запятой. Если необходимо передать параметр состоящий из нескольких слов, то их необходимо взять в двойные кавычки, и тогда эти слова будут считаться как один параметр. Например, на рисунке изображен запуск программы, с передачей ей аргумента, состоящего из двух слов — It work .

Рисунок 3 — Параметры функции main

А если убрать кавычки. То увидим только слово It . Если не планируется передавать какую-либо информацию при запуске программы, то можно удалить аргументы в функции main() , также можно менять имена данных аргументов. Иногда встречается модификации параметров argc и argv , но это все зависит от типа создаваемого приложения или от среды разработки.

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

1. Создание функций с переменным числом аргументов

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

Но для начала, вспомним как мы создаём функции обычным образом:

// функция с двумя необязательными параметрами function foo($arg1 = "", $arg2 = "") { echo "arg1: $arg1\n"; echo "arg2: $arg2\n"; } foo("hello","world"); /* выведет: arg1: hello arg2: world */ foo(); /* выведет: arg1: arg2: */

Теперь посмотрим на то, как можно написать функцию с неограниченным количеством аргументов. Для этого будет использовать метод func_get_args() :

// не указываем аргументы function foo() { // возвращает массив, переданных аргументов $args = func_get_args(); foreach ($args as $k => $v) { echo "arg".($k+1).": $v\n"; } } foo(); /* ничего не выведет */ foo("hello"); /* выведет arg1: hello */ foo("hello", "world", "again"); /* выведет arg1: hello arg2: world arg3: again */

2. Используем Glob() для поиска файлов

Часто названия функций говорят сами за себя. Такого нельзя сказать о функции glob() .

Если не вдаваться в подробности, её функциональность схожа с методом scandir() . Она позволяет найти необходимый файл по шаблону:

// найти все php файлы $files = glob("*.php"); print_r($files); /* выведет: Array ( => phptest.php => pi.php => post_output.php => test.php) */

Для нахождения файлов нескольких типов надо писать так:

// найти все php и txt файлы $files = glob("*.{php,txt}", GLOB_BRACE); print_r($files); /* на выходе: Array ( => phptest.php => pi.php => post_output.php => test.php => log.txt => test.txt) */

Так же можно в шаблоне указать путь:

$files = glob("../images/a*.jpg"); print_r($files); /* на выходе: Array ( => ../images/apple.jpg => ../images/art.jpg) */

Для того чтобы получить полный путь к документу используйте метод realpath() :

$files = glob("../images/a*.jpg"); // Применить функцию "realpath" к каждому элементу массива $files = array_map("realpath",$files); print_r($files); /* выведет: Array ( => C:\wamp\www\images\apple.jpg => C:\wamp\www\images\art.jpg) */

3. Информация об используемой памяти

Если вы будете отслеживать количество памяти, которое съедается на работу ваших скриптов то, наверное, чаще будете их оптимизировать.

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

Echo "Initial: ".memory_get_usage()." bytes \n"; /* Initial: 361400 bytes */ // дадим небольшую нагрузку for ($i = 0; $i < 100000; $i++) { $array = md5($i); } // и ещё for ($i = 0; $i < 100000; $i++) { unset($array[$i]); } echo "Final: ".memory_get_usage()." bytes \n"; /* Final: 885912 bytes */ echo "Peak: ".memory_get_peak_usage()." bytes \n"; /* Peak: 13687072 bytes */

4. Информация о процессоре

Для этого необходимо использовать метод getrusage() . Но учтите, что на Windows эта функция работать не будет.

Print_r(getrusage()); /* prints Array ( => 0 => 0 => 2 => 3 => 12692 => 764 => 3864 => 94 => 0 => 1 => 67 => 4 => 0 => 0 => 0 => 6269 => 0) */

Картина, изложенная выше, будет понятно тем, у кого есть опыт в системном администрировании. Для всех остальных предлагаем расшифровку:

  • ru_oublock: количество операций блочной записи
  • ru_inblock: количество операций блочного чтения
  • ru_msgsnd: количество отправленных сообщений
  • ru_msgrcv: количество принятых сообщений
  • ru_maxrss: максимальный размер невыгружаемого набора
  • ru_ixrss: общий объем разделяемой памяти
  • ru_idrss: общий объем неразделяемых данных
  • ru_minflt: количество используемых страниц памяти
  • ru_majflt: количество ошибок отсутствия страниц
  • ru_nsignals: количество принятых сигналов
  • ru_nvcsw: количество переключений контекста процессом
  • ru_nivcsw: количество принудительных переключений контекста
  • ru_nswap: количество обращений к диску при подкачке страниц
  • ru_utime.tv_usec: время работы в пользовательском режиме (микросекунды)
  • ru_utime.tv_sec: время работы в пользовательском режиме (секунды)
  • ru_stime.tv_usec: время работы в привилегированном режиме (микросекунды)
  • ru_stime.tv_sec: время работы в привилегированном режиме (секунды)

Для того чтобы узнать какие ресурсы вашего процессора используются скриптом, вам необходимо значение ‘user time’ (время работы в пользовательском режиме) и ’system time’ (время работы в привилегированном режиме). Вы можете получить результат как в секундах, так и в микросекундах. Для того чтобы превратить общее количество секунд в десятичное число, вам необходимо разделить значение микросекунд на 1 миллион и добавить к значению секунд.

Запутанно как-то. Вот пример:

// отдыхаем 3 секунды sleep(3); $data = getrusage(); echo "User time: ". ($data["ru_utime.tv_sec"] + $data["ru_utime.tv_usec"] / 1000000); echo "System time: ". ($data["ru_stime.tv_sec"] + $data["ru_stime.tv_usec"] / 1000000); /* выводит User time: 0.011552 System time: 0 */

Хотя выполнение скрипта заняло около 3-х секунд, процессор не был сильно нагружен. Дело в том, что при вызове (sleep) скрипт практически не потребляет ресурсов процессора. Вообще существует множество задач, которые занимают значительное время, но при этом не используют процессор. К примеру, ожидание операций связанных с диском. Так что вы не всегда используете процессорное время в своих скриптах.

Вот ещё пример:

// пройтись 10 миллионов раз for($i=0;$i<10000000;$i++) { } $data = getrusage(); echo "User time: ". ($data["ru_utime.tv_sec"] + $data["ru_utime.tv_usec"] / 1000000); echo "System time: ". ($data["ru_stime.tv_sec"] + $data["ru_stime.tv_usec"] / 1000000); /* выводит User time: 1.424592 System time: 0.004204 */

Работа скрипта заняла 1.4 секунды процессорного времени. В данном случае, время системных вызовов вообще низкое.

Время работы в привилегированном режиме (System Time) - это время, которое процессор затрачивает на выполнение системных запросов к ядру от имени программы. Пример:

$start = microtime(true); // вызываем microtime каждые 3 секунды while(microtime(true) - $start < 3) { } $data = getrusage(); echo "User time: ". ($data["ru_utime.tv_sec"] + $data["ru_utime.tv_usec"] / 1000000); echo "System time: ". ($data["ru_stime.tv_sec"] + $data["ru_stime.tv_usec"] / 1000000); /* выводит User time: 1.088171 System time: 1.675315 */

Теперь системного времени затратилось намного больше, чем в прошлом примере. Всё благодаря методу microtime(), который использует ресурсы системы.

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

5. Магические константы

В PHP существует множество магических констант, таких как номер текущей строки (__LINE__), путь к файлу (__FILE__), путь к каталогу (__DIR__), имя функции (__FUNCTION__), имя класса (__CLASS__), имя метода (__METHOD__) и пространства имён (__NAMESPACE__).

Все мы их рассматривать не будем. Посмотрим только лишь парочку:

// этот скрипт зависит от текущего расположения файла и // может вызвать проблемы, если его использовать из разных дирректорий require_once("config/database.php"); // этот скрипт не вызовет проблем require_once(dirname(__FILE__) . "/config/database.php");

Используйте __LINE__ при отладке скриптов:

// код // ... my_debug("some debug message", __LINE__); /* выведет Line 4: some debug message */ // ещё код // ... my_debug("another debug message", __LINE__); /* выведет Line 11: another debug message */ function my_debug($msg, $line) { echo "Line $line: $msg\n"; }

6. Генерирование уникальных ID

Бывают такие моменты, когда вам надо сгенерировать уникальную строку. Множество раз я видел, что для решения этой задачи используют функцию md5():

// генерируем случайную строку echo md5(time() . mt_rand(1,1000000));

Но на самом деле для этих целей в PHP есть специальная функция uniqid()

// генерируем случайную строку echo uniqid(); /* выведет 4bd67c947233e */ // ещё разок echo uniqid(); /* выведет 4bd67c9472340 */

Невооружённым взглядом можно заметить, что первые символы мягко говоря схожи… Так происходит из-за того, что данный метод использует время сервера для генерации символов. Это даже полезно, т.к. все сгенерированные значения получаются в алфавитном порядке, что даёт возможность быстро их сортировать.

Для того чтобы уменьшить шансы получения дубликата, мы можем добавить префикс или использовать второй параметр (увеличит количество символов):

// с префиксом echo uniqid("foo_"); /* выведет foo_4bd67d6cd8b8f */ // со вторым параметром echo uniqid("",true); /* выведет 4bd67d6cd8b926.12135106 */ // оба echo uniqid("bar_",true); /* выведет bar_4bd67da367b650.43684647 */

Этот метод генерирует строки размером меньше, чем md5, тем самым вы сможете сэкономить место.

7. Сериализация

Вам когда-нибудь приходилось хранить комплексные данные в базе или в файле? Для того чтобы сконвертировать объект в строку в PHP предусмотрена специальная функция.

Вообще говоря, этих методов 2: serialize() и unserialize()

// сложный массив $myvar = array("hello", 42, array(1,"two"), "apple"); // конвертируем в строку $string = serialize($myvar); echo $string; /* выведет a:4:{i:0;s:5:"hello";i:1;i:42;i:2;a:2:{i:0;i:1;i:1;s:3:"two";}i:3;s:5:"apple";} */ // получаем исходное значение $newvar = unserialize($string); print_r($newvar); /* выведет Array ( => hello => 42 => Array ( => 1 => two) => apple) */

Вот так вот работают эти функции. Однако из-за бурного роста популярности JSON, в PHP 5.2 были добавлены 2 метода json_encode() и json_decode(). Их работа схожа с serialize():

// сложные массив $myvar = array("hello", 42, array(1,"two"), "apple"); // конвертируем в строку $string = json_encode($myvar); echo $string; /* выведет ["hello",42,,"apple"] */ // восстанавливаем исходное значение $newvar = json_decode($string); print_r($newvar); /* prints Array ( => hello => 42 => Array ( => 1 => two) => apple) */

Этот вариант более компактный и совместимый с другими языками, такими как JavaScript. Однако при работе с очень навороченными объектами может возникнуть потеря данных.

8. Сжатие строк

Кода мы говорим о сжатии, то на ум сразу же приходят архивные файлы в формате ZIP. PHP предоставляет возможность сжатия длинных строк без всяких файлов.

В следующем примере продемонстрируем работу функций gzcompress() и gzuncompress() :

$string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ut elit id mi ultricies adipiscing. Nulla facilisi. Praesent pulvinar, sapien vel feugiat vestibulum, nulla dui pretium orci, non ultricies elit lacus quis ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam pretium ullamcorper urna quis iaculis. Etiam ac massa sed turpis tempor luctus. Curabitur sed nibh eu elit mollis congue. Praesent ipsum diam, consectetur vitae ornare a, aliquam a nunc. In id magna pellentesque tellus posuere adipiscing. Sed non mi metus, at lacinia augue. Sed magna nisi, ornare in mollis in, mollis sed nunc. Etiam at justo in leo congue mollis. Nullam in neque eget metus hendrerit scelerisque eu non enim. Ut malesuada lacus eu nulla bibendum id euismod urna sodales. "; $compressed = gzcompress($string); echo "Original size: ". strlen($string)."\n"; /* выведет Original size: 800 */ echo "Compressed size: ". strlen($compressed)."\n"; /* выведет Compressed size: 418 */ // возвращаем $original = gzuncompress($compressed);

В наших силах уменьшить объём текста на 50%. В этих же целях можно использовать методы gzencode() и gzdecode(), которые используют другой алгоритм сжатия.

9. Выполнить перед завершением

В PHP существует функция register_shutdown_function() , которая позволит вам выполнить какой-то код перед завершением работы скрипта.

Допустим, вы хотите узнать какую-то информацию… Время работы скрипта:

// получаем время начала $start_time = microtime(true); // какие-то операции // ... // выводим время работы echo "execution took: ". (microtime(true) - $start_time). " seconds.";

На первый взгляд это может показаться тривиальной задачей. Для этих целей, вы можете поместить код в конце файла. Однако если перед этим где-то сработает функция exit(), этот код никогда не сработает. Так же, он не сработает если на странице будет ошибка или пользователь прервёт загрузку страницы (нажав на соответствующую кнопку в своём браузере);

При использовании метода register_shutdown_function() код выполнится в любом случае:

$start_time = microtime(true); register_shutdown_function("my_shutdown"); function my_shutdown() { global $start_time; echo "execution took: ". (microtime(true) - $start_time). " seconds."; }

Вывод

PHP это целая планета, которая не перестаёт нас удивлять своим содержимым. А что думаете вы о данных функциях?

Htaccess - это дополнительный конфигурационный файл Apache, который позволяет настраивать работу веб-сервера для каждой отдельной директории, не влияя на глобальные настройки Apache. Локальная аналогия httpd.conf . Обычно он отвечает за редиректы и управление доступом к директориям.

Название начинается с точки. Можно сказать, это файл без названия с расширением htaccess.

Настройки.htaccess действуют на каталог, в котором он расположен, и на все дочерние каталоги. Создайте файл и поместите в нужную вам директорию. Например, в корень проекта.

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

mod_rewrite и редиректы

Убедитесь, что в конфигурационном файле Apache httpd.conf активирован mod_rewrite . То есть, раскомментирована соответствующая строка:

LoadModule rewrite_module modules/mod_rewrite.so

Или, если не хотите открывать в текстовом редакторе файл, можно воспользоваться командой в терминале:

Sudo a2enmod rewrite

mod_rewrite - это модуль Apache, предназначенный для преобразования URL-ов. Рассмотрим на примере, как он работает. Допустим, пользователь вводит следующий адрес:

C помощью mod_rewrite можно отправить содержание с другого URL, например такого:

Http://www.example.com/public/src/view/page.html

Зачем это нам? Легко догадаться, что писать полный путь до страницы долго и просто неудобно. Посетителям сайта не нужно думать о внутренней структуре сайта - им важно максимально быстро попасть на искомую страницу.

В адресной строке пользователь будет всё также видеть введенное им:

Http://www.example.com/page.html

Это пример самого простого редиректа.

Сразу к практике

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

Php_value short_open_tag 1 php_value upload_max_filesize 10M php_value post_max_size 10M RewriteEngine On RewriteBase / RewriteRule ^(application|modules|system) - RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php/$0

  • php_value установка строковых и числовых значений
  • php_flag устанавливает логические значения (да/нет)

Общий синтаксис директив

Php_value/php_flag имя_директивы_php flag/value

Директива short_open_tag разрешает использование короткого синтаксиса для оформления PHP-кода:

Php_value short_open_tag 1

upload_max_filesize определяет максимальный размер загружаемого файла.

Php_value upload_max_filesize 10M

А post_max_size устанавливает максимально допустимый размер данных, отправляемых методом POST.

Php_value post_max_size 10M

RewriteEngine

Включает/выключает механизм mod_rewrite .

RewriteEngine On

RewriteRule

RewriteRule просто преобразовывает строку в соответствии с регулярными выражениями.

Синтаксис: RewriteRule regular_expression

# На входе RewriteRule "index.php" RewriteRule ^index.php main.php [R] # На выходе: "index.php" -> "main.php"

Мы преобразовали index.php в main.php и выполнили редирект.

Важно : RewriteRule обычно принимает два аргумента: что нужно заменить и на что нужно заменить. Если нам не нужно выполнять замену то можно записать в виде:

Символ «-» означает «не преобразовывать»

RewriteBase

После всех RewriteRule, в силу вступает RewriteBase. Если получившийся после преобразований запрос является относительным и отличается от исходного, RewriteBase восстановит его, сделав абсолютным. RewriteBase просто допишет себя к запросу слева. Потому что значение RewriteBase - путь от корня сайта до.htaccess. В нашем случае.htaccess лежит прямо в корне, поэтому:

Синтаксис : RewriteBase URL-path-from-.htaccess-file-to-site-root

Например:

# .htaccess находится в /dir/ # Путь от корня сайта до.htaccess /dir/ RewriteBase /dir/ # Запрос http://example.com/dir/logo.gif # На вход RewriteRule попадает "logo.gif" RewriteRule ^logo.gif$ logo-orange.gif # После RewriteRule: "logo.gif" -> "logo-orange.gif" # После RewriteBase: "logo-orange.gif" -> "/dir/logo-orange.gif"

Regular expressions

Регулярные выражения, которые вам могут встретиться в.htaccess.

Символ Значение Пример
. Один любой символ c.t это cat , cot , cut , и т. д.
+ Один или несколько одинаковых символов a+ это a , aa , aaa , и т. д.
* Ноль или несколько одинаковых символов a* работает также как и a+ но в случае a* условию удовлетворит и пустая строка
? Совпадение опционально colou?r подойдет как color , так и colour .
^ Символ, с которого начинается строка ^a соответствует строка, которая начинается с a
$ Символ, которым заканчивается строка a$ соответствует строка, которая заканчивается a .
() Находит и запоминает соответствие группы символов.

Также может быть использовано для Back-Reference (смотри пример)

(ab)+ удовлетворит ababab

Back-Reference example:

RewriteRule ^/(+) /(.*) $ /home?page=$1 &id=$2

/album/123 → /home?page=album &id=123

Один из возможных символов ct подойдет cut , cot или cat .

Больше regular expressions

Флаги

Синтаксис : RewriteRule regular_expression [флаг1,флаг2,флаг3]

Флаг Описание
[F] Forbidden - возвращает ошибку 403 Forbidden (запрещено).
[L] Last - остановить процесс преобразования на этом месте и не применять больше никаких правил преобразований.
Query String Append - этот флаг указывает механизму преобразований на добавление, а не замену , строки запроса из URL к существующей, в строке подстановки.
PassThrough - останавливает процесс преобразования и передает полученную новую ссылку дальше по цепочке.
[R] Redirect - останавливает процесс преобразования и возвращает результат браузеру клиента как редирект на новую страницу.
[S] Skip - пропускает следующее правило, если текущее правило сработало. Можно указать количество последующих игнорируемых правил .

Бывает, что данные в программу передаются из командной строки при ее вызове. Такие данные называются аргументами командной строки. Выглядит это так, например:

./a.out test.txt ls -lt /home/peter/

Здесь вызываются программы a.out (из текущего каталога) и ls (из одного каталога, указанного в переменной окружения PATH). Первая программа из командной строки получает одно слово - test.txt, вторая - два: -lt и /home/peter/.

Если программа написана на языке C, то при ее запуске управление сразу передается в функцию main() , следовательно, именно она получает аргументы командной строки, которые присваиваются ее переменным-параметрам.

До этого мы определяли функцию main() так, как-будто она не принимает никакие параметры и ничего не возвращает. На самом деле в языке C любая функция по-умолчанию (если не определено ничего иного) возвращает целое число. В этом можно убедиться. Если записать код таким образом:

main() { printf ("Hi\n " ) ; return 0 ; }

То никакого предупреждения или ошибки при компиляции не возникнет. То же самое будет, если записать int main() . Это доказывает, что функция по-умолчанию возвращает целое число, а не ничто (void). Хотя то, что возвращает функция всегда можно "переопределить", например, voidmain() или float main() .

При вызове программы из командной строки в нее всегда передается пара данных:

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

Следует иметь в виду, что само имя программы также считается. Например, если вызов выглядит так:

./a.out 12 theme 2

То первый аргумент программы имеет значение 4, а массив строк определяется как {"./a.out", "12", "theme", "2"}.

Обратите внимание на терминологию, есть всего два аргумента программы (число и массив), но сколько угодно аргументов командной строки. Аргументы командной строки "преобразуются" в аргументы программы (в аргументы функции main()).
Эти данные (число и указатель) передаются в программу даже тогда, когда она просто вызывается по имени без передачи в нее чего-либо: ./a.out. В таком случае первый аргумент имеет значение 1, а второй указывает на массив, состоящий всего из одной строки {"./a.out"}.

То, что в программу передаются данные, вовсе не означает, что функция main() должна их принимать. Если функция main() определена без параметров, то получить доступ к аргументам командной строки невозможно. Хотя ничего вам не мешает их передавать. Ошибки не возникнет.

Чтобы получить доступ к переданным в программу данным, их необходимо присвоить переменным. Поскольку аргументы сразу передаются в main() , то ее заголовок должен выглядеть таким образом:
main (int n, char *arr)

В первой переменной (n) содержится количество слов, а во второй - указатель на массив строк. Часто второй параметр записывают в виде **arr . Однако это то же самое. Вспомним, что сам массив строк, содержит в качестве своих элементов указатели на строки. А в функцию мы передаем указатель на первый элемент массива. Получается, что передаем указатель на указатель, т.е. **arr .

Задание
Напишите такую программу:

#include int main(int argc, char ** argv) { int i; printf ("%d\n " , argc) ; for (i= 0 ; i < argc; i++ ) puts (argv[ i] ) ; }

Она выводит количество слов в командной строке при ее вызове и каждое слово с новой строки. Вызовите ее без аргументов командной строки и с аргументами.

В программе мы использовали переменные-параметры argc и argv. Принято использовать именно такие имена, но на самом деле они могут быть любыми. Лучше придерживаться этого стандарта, чтобы ваши программы были более понятны не только вам, но и другим программистам.

Практическое значение передачи данных в программу

Если у вас есть опыт работы в командной строке GNU/Linux, вы знаете, что у большинства команд есть ключи и аргументы. Например, при просмотре содержимого каталогов, копировании, перемещении в качестве аргументов указываются объекты файловой системы, над которыми выполняется команда. Особенности ее выполнения определяются с помощью ключей. Например, в команде

Cp -r ../les_1 ../les_101

cp - это имя команды, -r - ключ, а../les_1 и../les_101 - аргументы команды.

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

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

#include #include main (int argc, char ** argv) { int i, ch; FILE * f[ 5 ] ; if (argc < 3 || argc > 7 ) { puts ("Неверное количество параметров" ) ; return 1 ; } if (strcmp (argv[ 1 ] , "-w" ) != 0 && strcmp (argv[ 1 ] , "-a" ) != 0 ) { puts ("Первый параметр может быть либо -w, либо -a" ) ; return 2 ; } for (i= 0 ; i < argc- 2 ; i++ ) { f[ i] = fopen (argv[ i+ 2 ] , argv[ 1 ] + 1 ) ; if (f[ i] == NULL) { printf ("Файл %s нельзя открыть\n " , argv[ i+ 2 ] ) ; return 3 ; } } while ((ch = getchar () ) != EOF) for (i= 0 ; i < argc- 2 ; i++ ) putc (ch, f[ i] ) ; for (i= 0 ; i < argc- 2 ; i++ ) fclose (f[ i] ) ; return 0 ; }

Пояснения к коду:

  1. Создается массив из пяти файловых указателей. Следовательно можно одновременно открыть не более пяти файлов. Файловый указатель первого файла будет хранится в элементе массива f, второго - в f и т.д.
  2. Проверяется количество аргументов командной строки. Их должно быть не меньше трех, т.к. первый - это имя программы, второй - режим открытия файла, третий - первый или единственный файл, в который будет производится запись. Поскольку программа позволяет открыть только пять файлов, то общее число аргументов командной строки не может быть больше семи. Поэтому если количество аргументов меньше 3 или больше 7, то программа завершается, т.к. оператор return приводит к выходу из функции, даже если после него есть еще код. Возвращаемое из функции значение неравное 0, может быть интерпретировано родительским процессом, как сообщение о том, что программа завершилась с ошибкой.
  3. Проверяется корректность второго аргумента командной строки. Если он не равен ни "-w", ни "-a", то условное выражение во втором if возвращает 1 (true). Функция strcmp() позволяет сравнивать строки и возвращает 0 в случае их равенства.
  4. В цикле for открываются файлы по указанным адресам, которые начинаются с третьего элемента массива argv. Именно поэтому к i прибавляется 2, чтобы получать элементы массива argv, начиная с третьего. Выражение argc-2 указывает на количество переданных имен файлов; т.к. в argc хранится общее число аргументов командной строки, первые два из которых не являются именами файлов.
  5. Выражение argv+1 позволяет "вырезать" из строки "-w" (или "-a") подстроку "w" (или "a"), т.к. argv по сути указатель на первый элемент строки. Прибавляя к указателю единицу, мы смещаем его к следующему элементу массива.
  6. Если файл отрыть не удается, то функция fopen() возвращает NULL. В таком случае программа завершается.
  7. Каждый символ, введенный пользователем с клавиатуры, записывается во все открытые файлы.
  8. В конце файлы закрываются.

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

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

Я предполагаю, что читатель уже знаком с тем, что такое mod_rewrite, и не буду описывать его основы, которые легко найти в интернете. Также нужно отметить, что в статье освещается работа mod_rewrite при использовании его директив в файле.htaccess. Отличия при работе в контексте изложены в .

Итак, вы изучили mod_rewrite, составили несколько RewriteRule и успели столкнуться с бесконечными перенаправлениями, со случаем, когда правило почему-то не ловит ваш запрос, а также с непредсказуемой работой группы правил, когда последующее правило неожиданно изменяет запрос, кропотливо подготовленный правилами предыдущими.

С чем работает RewriteRule

Первому RewriteRule передается путь от того места, где находится.htaccess, до запрошенного файла. Эта строка никогда не начинается со "/". Последующим RewriteRule передается результат предыдущих преобразований.

Чтобы досконально понять, как работает RewriteRule, необходимо сначала определить, с чем он работает. Рассмотрим, как Apache получает строку, которая изначально передается на обработку RewriteRule в.htaccess.

Когда только начинаешь работать с mod_rewrite, логично предполагаешь, что он работает со ссылками. Однако в случае с использованием mod_rewrite в.htaccess это не так. На самом деле в RewriteRule передается не ссылка, а путь до запрошенного файла.

Из-за внутренней архитектуры Apache в тот момент, когда в действие вступает.htaccess, mod_rewrite может оперировать только с путем до файла, который должен быть обработан. Это связано с тем, что до передачи в mod_rewrite запрос уже могли изменить другие модули (например, mod_alias), и итоговый путь до файла на сайте уже может не совпадать с исходной ссылкой. Если бы mod_rewrite работал с исходной ссылкой, он бы нарушал действие модулей, которые изменили запрос до него.

Поэтому в mod_rewrite передается абсолютный путь до файла, который должен быть обработан. Также mod_rewrite знает путь до.htaccess, в котором размещены правила RewriteRule. Чтобы сделать из пути до файла что-то похожее на ссылку, с которой планирует работать разработчик сайта, mod_rewrite отрезает от абсолютного пути часть до файла.htaccess.

Так вот, именно этот путь, от которого отрезан путь до.htaccess, передается в первый RewriteRule. Например:

Запрос: http://example.com/templates/silver/images/logo.gif DocumentRoot: /var/www/example.com Путь до файла: /var/www/example.com/templates/silver/images/logo.gif .htaccess находится в: /var/www/example.com/templates/.htaccess

В первый RewriteRule будет передано: silver/images/logo.gif Обратите внимание: «templates/» тоже отрезалось. как работает RewriteRule Путь до.htaccess отрезается вместе со слешем. Из этого есть следствие: строка, которая изначально передается на обработку RewriteRule никогда не начинается со "/".

Важно запомнить, что не делает RewriteRule. Она не обрабатывает имя сайта, аргументы, которые переданы в скрипт, да и ссылку обрабатывает не всю, если.htaccess размещен не в корне сайта. Всем этим занимается RewriteCond, которого кратко коснемся чуть позже. Итак:

# работать не будет - правило начинается со / RewriteRule ^/index.php$ /my-index.php # работать не будет - название сайта не анализируется RewriteRule RewriteRule ^example.com/.* http://www.example.com # работать не будет - аргументы ссылки не попадают в RewriteRule RewriteRule index.php\?newspage=(+) news.php?page=$1 # Будет работать только если.htaccess находится там же, где находится папка templates, # например, в корне сайта. То есть, если.htaccess находится в templates/.htaccess , правило # работать НЕ БУДЕТ, потому что mod_rewrite отрежет путь до.htaccess и на вход RewriteRule # строка попадет уже без "templates/" RewriteRule ^templates/common/yandex-money.gif$ templates/shared/yad.gif

С чем работает RewriteRule, мы разобрались. Теперь посмотрим, как он работает.

Как работает RewriteRule

RewriteRule просто преобразовывает строку в соответствии с регулярными выражениями, и все. RewriteRule работает со строкой, а не со ссылкой или путем до файла.

Как мы выяснили выше, на вход RewriteRule попадает путь от.htaccess до запрошенного файла. Удобнее всего теперь абстрагироваться от путей и ссылок и рассматривать то, с чем работает RewriteRule, как обычную строку. Эта строка передается от RewriteRule к RewriteRule, видоизменяясь, если какое-то из RewriteRule сработало.

В общем виде, если исключить сложности с использованием флагов (некоторые из которых мы рассмотрим ниже) и сложности с составлением регулярных выражений (которых мы почти не будем касаться в этой статье), RewriteRule работает ОЧЕНЬ просто. Взяли строку. Сравнили с регулярным выражением в первом аргументе. Если есть совпадение - заменили всю строку на значение второго аргумента. Передали строку следующему RewriteRule. Вот, в общем, и все. Чтобы наглядно проиллюстрировать, что RewriteRule работает именно со строкой, рассмотрим следующий фантастический пример:

# Запрос: http://mysite.com/info.html # В первый RewriteRule попадет "info.html" # Преобразовываем запрос в произвольную строку. RewriteRule ^info.html$ "I saw a turtle in the hole. And it was dancing rock-n-roll. And it was smiling. All in all, it was a very funny doll." # "info.html" -> "I saw a turtle..." # Заменяем эту строку на внешнюю ссылку. RewriteRule turtle https://example.com/information/index.html # "I saw a turtle..." -> "https://example.com/information/index.html" # Заменяем имя сайта! RewriteRule ^(.*)example.com(.*)$ $1example.org$2 # "https://example.com/information/index.html" -> "https://example.org/information/index.html" # Заменяем протокол! RewriteRule ^https:(.*)$ ftp:$1 # "https://example.org/information/index.html" -> "ftp://example.org/information/index.html" # Заменяем конечную ссылку. RewriteRule ^(.*)/index.html$ $1/main.php # "ftp://example.org/information/index.html" -> "ftp://example.org/information/main.php"

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

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

Https://

или аналоги (запомнит, что мы хотели сделать внешний редирект) и на символ "?" (посчитает следующие символы аргументами, которые нужно будет подставить к запросу). Однако сейчас нас это не интересует - важно понять, что в RewriteRule нет никакой магии - она просто берет строку и изменяет ее так, как вы ей сказали. Внешние редиректы и аргументы мы рассмотрим позже в статье, там тоже есть, о чем поговорить.

После того как все преобразования произведены и выполнено последнее RewriteRule, вступает в силу RewriteBase.

Для чего нужен RewriteBase

Если получившийся после преобразований запрос является относительным и отличается от исходного, RewriteBase добавит себя к нему слева. Нужно обязательно указывать RewriteBase в.htaccess. Его значение - путь от корня сайта до.htaccess. RewriteBase выполняется только после всех RewriteRule, а не между ними.

Мы уже говорили выше о том, что в mod_rewrite, работающий в.htaccess, попадает абсолютный путь до запрошенного файла. Чтобы передать его в RewriteRule, mod_rewrite отрезает путь до.htaccess. Потом правила RewriteRule одно за одним последовательно изменяют запрос. И вот после того, как запрос изменен, Apache должен восстановить абсолютный путь до файла, который он должен в итоге обработать. RewriteBase фактически является хаком, который помогает восстановить исходный путь до файла.

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

После всех преобразований RewriteBase смотрит, относительный получился в итоге путь или абсолютный. В контексте Apache имеется в виду относительный или абсолютный путь, отсчитывая от корня сайта: images/logo.gif - относительный. /images/logo.gif - абсолютный (в начале слеш). http://example.com/images/logo.gif - самый абсолютный из всех. Если путь абсолютный, RewriteBase ничего не делает. А если относительный - RewriteBase дописывает себя слева. Это работает как для внутренних, так и для внешних редиректов:

# .htaccess находится в /images/ # RewriteBase указан /images/ RewriteBase /images/ # Запрос http://example.com/images/logo.gif # На вход RewriteRule попадает "logo.gif" RewriteRule ^logo.gif$ logo-orange.gif # После RewriteRule: "logo.gif" -> "logo-orange.gif" # После RewriteBase: "logo-orange.gif" -> "/images/logo-orange.gif" # Запрос http://example.com/images/header.png # На вход RewriteRule попадает "header.png" RewriteRule ^header.png$ /templates/rebranding/header.png # После RewriteRule: "header.png" -> "/templates/rebranding/header.png" # После RewriteBase: ничего не меняется, так итоговый результат преобразований начинается со "/". # Запрос http://example.com/images/director.tiff # На вход RewriteRule попадает "director.tiff" # Используем внешний относительный редирект RewriteRule ^director.tiff$ staff/manager/director.tiff # После RewriteRule: "director.tiff" -> "staff/manager/director.tiff" # + mod_rewrite запомнил, что будет внешний редирект # После RewriteBase: "staff/manager/director.tiff" -> "/images/staff/manager/director.tiff" # mod_rewrite вспомнил про внешний редирект: # "/images/staff/manager/director.tiff" -> http://example.com/images/staff/manager/director.tiff

Обычно после некоторого знакомства с mod_rewrite складывается следующая привычка:

    в каждый.htaccess добавлять «RewriteBase /»

    все перенаправления начинать со слеша: «RewriteRule news.php /index.php?act=news». Это помогает избавиться от артефактов работы RewriteBase, но так делать неправильно. Теперь, когда нам известно, что делает RewriteBase, можно сформулировать следующие корректные правила:

RewriteBase должен совпадать с путем от корня сайта до.htaccess. Начинать перенаправления со "/" нужно только тогда, когда необходимо указать абсолютный путь от корня сайта до файла.

Что будет, если не указать RewriteBase? По умолчанию Apache делает его равным абсолютному пути на файловой системе до.htaccess (например, /var/www/example.com/templates/). Некорректность такого предположения Apache проявляется на внешних относительных редиректах:

# Запрос http://example.com/index.php # DocumentRoot: /var/www/example.com/ # .htaccess находится в корне сайта, и в нем НЕ УКАЗАН RewriteBase. # Поэтому по умолчанию RewriteBase равен абсолютному пути до.htaccess: /var/www/example.com/ # На входе RewriteRule - "index.php" RewriteRule ^index.php main.php [R] # На выходе: "index.php" -> "main.php" # mod_rewrite запомнил, что нужен внешний редирект # Закончились RewriteRule # mod_rewrite все равно выполняет RewriteBase, так как у него есть значение по умолчанию. # Получается: "main.php" -> "/var/www/example.com/main.php" # Здесь mod_rewrite вспоминает, что был внешний редирект: # "/var/www/example.com/main.php" -> http://example.com/var/www/example.com/main.php # Получилось совсем не то, что имели в виду.

Итак, запрос прошел через все RewriteRule, после чего к нему, в случае необходимости, добавился RewriteBase. Должен ли теперь Apache отдать файл, на который показывает результирующий путь? Нет. Теперь получившийся запрос будет обрабатываться еще раз .

Как работает mod_rewrite. Флаг [L]

mod_rewrite запускает обработку запроса снова и снова, до тех пор, пока он не перестанет меняться. И флаг [L] не может это остановить.

При составлении более-менее сложных конфигураций mod_rewrite важно понимать, что изменение запроса не заканчивается на последнем RewriteRule . После того, как сработало последнее правило RewriteRule и был добавлен RewriteBase, mod_rewrite смотрит, изменился запрос или нет. Если запрос изменился, его обработка начинается заново с начала.htaccess.

Apache поступает так, потому что в процессе изменения запроса он мог быть перенаправлен в другую директорию. В ней может быть собственный.htaccess, который не участвовал в предыдущей обработке запроса. В этом же новом.htaccess могут быть правила, которые влияют на обработку запроса - как правила mod_rewrite, так и правила других модулей. Чтобы корректно обработать эту ситуацию, Apache должен запустить весь цикл обработки заново.

Постойте, но ведь есть флаг [L] , который останавливает обработку запроса mod_rewrite"ом!

Не совсем так. Флаг [L] останавливает текущую итерацию обработки запроса. Однако если запрос был изменен теми RewriteRule, которые все-таки успели отработать, Apache запустит цикл обработки запроса заново с первого RewriteRule.

# Запрос: http://example.com/a.html RewriteBase / RewriteRule ^a.html$ b.html [L] RewriteRule ^b.html$ a.html [L]

Пример выше приведет к бесконечному циклу перенаправлений и к «Internal Server Error» в итоге. В этом примере бесконечный цикл очевиден, однако в более сложных конфигурациях может потребоваться покопаться в правилах, чтобы определить, какие запросы зацикливаются между собой.

Чтобы избежать подобных ситуаций, рекомендуется использовать флаг [L] только при необходимости. Необходимость может быть двух типов: Когда используется внешний редирект - или . В случае внешнего редиректа дальнейшая обработка запроса нежелательна (см. ниже про флаг [R] ), и ее лучше остановить. Когда в.htaccess есть зацикливание, от которого не избавиться, и обработку запроса mod_rewrite"ом нужно принудительно прекратить. В этом случае используется специальная конструкция - см. в конце статьи советы на эту тему.

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

# Запрос: http://example.com/a.html # Начало.htaccess RewriteBase / RewriteRule ^a.html$ b.html RewriteRule ^b.html$ a.html # Конец.htaccess

Отгадка: В результате выполнения всех RewriteRule запрос меняется таким образом, что конечный результат равен исходному. Apache видит это и не запускает повторную обработку запроса. Будет возвращен файл a.html.

Как работает mod_rewrite. Флаг [R]

    Флаг [R] не останавливает обработку запроса, возвращая сразу внешний редирект. Вместо этого он запоминает необходимость внешнего редиректа, и обработка запроса продолжается следующими RewriteRule. Рекомендуется всегда использовать с флагом [L] .

    Флаг [R] сообщает Apache, что нужно выполнить не внутренний, а внешний редирект. Чем отличается внешний редирект от внутреннего? Внутренний редирект просто изменяет путь до файла, который будет отдан пользователю, при этом пользователь считает, что получает тот файл, который он изначально запросил. При внешнем же редиректе Apache вместо содержимого файла возвращает пользователю статус ответа 301 или 302 и сообщает ссылку, по которой браузер должен обратиться для получения файла.

Казалось бы, при обработке флага [R] Apache должен сразу прекратить обработку RewriteRule и вернуть пользователю внешний редирект. Однако давайте вспомним фантастический пример из раздела «Как работает RewriteRule». В нем мы сначала указали флаг [R] , обозначив необходимость внешнего редиректа, после чего продолжили изменять ссылку следующими RewriteRule.

Именно так и работает Apache при указании внешнего редиректа. Он просто «помечает» себе, что после выполнения всех правил необходимо вернуть статус 302 (по умолчанию), но при этом продолжает выполнение всех RewriteRule дальше по списку. Мы можем и дальше изменять запрос как нам нужно, единственное, что не получится - сделать редирект обратно внутренним.

Тем не менее, вряд ли вы хотите после отдачи внешнего редиректа каким-либо образом изменять его. Поэтому рекомендуется при употреблении флага [R] указывать его совместно с [L] :

# BlackJack переехал на красивое имя RewriteRule ^bj/(.*) blackjack/$1 # Можно использовать просто внешнюю ссылку RewriteRule ^bj/(.*) http://blackjack.example.com/$1 [L]

Вместо использования флага [R] можно указывать просто внешнюю ссылку. В этом случае Apache сам догадается, что необходимо сделать внешний редирект. Здесь, как и с в случае с явным указанием флага [R] , рекомендуется использовать флаг [L] . Если внешний редирект ведет на тот же сайт, лучше использовать флаг [R] без указания полной ссылки (иными словами, использовать относительный внешний редирект). Это сделает правило независимым от имени сайта. Если же внешний редирект ведет на другой сайт, иначе, как указав полную внешнюю ссылку, это сделать не получится.

Как работает mod_rewrite. Указание параметров запроса и флаг

Изменение параметров запроса в RewriteRule не изменяет строку, с которой работает следующий RewriteRule. Однако при изменении параметров изменяется переменная %{QUERY_STRING}, с которой может работать RewriteCond.

Используемая терминология: «параметры» - параметры запроса, «аргументы» - аргументы RewriteRule.

С помощью RewriteRule можно изменять не только путь до файла, который будет обрабатываться, но и параметры запроса GET, которые будут ему передаваться. Это часто используется для передачи обработки ЧПУ в общий скрипт-обработчик, например: RewriteBase /

# Запрос: http://example.com/news/2010/07/12/grand-opening.html # На входе: "news/2010/07/12/grand-opening.html" RewriteRule ^news/(.*)$ index.php?act=news&what=$1 # После RewriteRule: "news/2010/07/12/grand-opening.html" -> "index.php" # %{QUERY_STRING}: "" -> "act=news&what=2010/07/12/grand-opening.html"

В момент, когда правило RewriteRule встречает вопросительный знак во втором аргументе, оно понимает, что происходит изменение параметров в запросе. В результате происходит следующее: RewriteRule заменяет строку, с которой оно работает, на часть второго аргумента до вопросительного знака. Обратите внимание, что новые параметры запроса не попадают в строку, с которой будут работать последующие правила RewriteRule. Часть второго аргумента после вопросительного знака попадает в переменную %{QUERY_STRING}. Если был указан флаг , параметры запроса будут добавлены в начало %{QUERY_STRING}. Если флаг указан не был, %{QUERY_STRING} полностью заменится параметрами запроса из RewriteRule. Еще пара примеров:

RewriteBase / # Запрос: http://example.com/news/2010/?page=2 # На входе RewriteRule: "news/2010/" RewriteRule ^news/(.*)$ index.php?act=news&what=$1 # После преобразования: "news/2010/" -> "index.php" # Значение %{QUERY_STRING}: "page=2" -> "act=news&what=2010/" Скорее всего, правило выше работает неправильно, так как теряется аргумент page. Исправим это: RewriteBase / # Запрос: http://example.com/news/2010/?page=2 # На входе RewriteRule: "news/2010/" RewriteRule ^news/(.*)$ index.php?act=news&what=$1 # После преобразования: "news/2010/" -> "index.php" # Значение %{QUERY_STRING}: "page=2" -> "act=news&what=2010/&page=2"

Важно понимать, что изменение параметров запроса изменяет %{QUERY_STRING}, который может использоваться в дальнейшем в RewriteCond. Это нужно учитывать при составлении последующих правил, проверяющих аргументы.

Конечно, изменяется, ведь запрос уходит на повторную обработку Apache"м!

Нет, %{QUERY_STRING} изменяется сразу же. Доказательство приводить не буду - про параметры и так уже написано больше, чем интересно читать:)

Что же делать, чтобы проверить в RewriteCond именно те параметры запроса, которые передал пользователь, а не модифицированные RewriteRule"ами? Смотрите советы в конце статьи.

RewriteCond и производительность

Сначала проверяется совпадение запроса с RewriteRule, а уже потом - дополнительные условия RewriteCond.

Пару слов стоит сказать о том, в каком порядке mod_rewrite выполняет директивы. Так как в.htaccess сначала идут RewriteCond, а потом RewriteRule, кажется, что mod_rewrite сначала проверяет все условия, а потом приступает к выполнению RewriteRule.

На самом деле все происходит наоборот. Сначала mod_rewrite проверяет, подходит ли текущее значение запроса под регулярное выражение RewriteRule, а уже потом будет проверять все условия, перечисленные в RewriteCond.

Так что если у вас в RewriteRule регулярное выражение на две страницы и вы, задумавшись о производительности, решили ограничить выполнение этого правила дополнительными RewriteCond, знайте - ничего не получится. В этом случае лучше использовать флаги RewriteRule [C] или [S], чтобы пропустить более сложное правило, если более простые проверки не сработали.

Переменные и флаги RewriteCond, остальные флаги RewriteRule и прочее

Читайте документацию.

Мы познакомились с принципами работы RewriteRule, RewriteBase, флагов [L] , [R] и , а также разобрали механизм обработки запросов внутри mod_rewrite. Из незатронутого остались: другие флаги RewriteRule, директивы RewriteCond и RewriteMap.

К счастью, эти директивы и флаги не таят в себе каких-либо загадок и работают именно так, как описано в большинстве учебников. Для их понимания достаточно почитать официальную документацию. В первую очередь рекомендую изучить список переменных, которые можно проверять в RewriteCond - %{QUERY_STING}, %{THE_REQUEST}, %{REMOTE_ADDR}, %{HTTP_HOST}, %{HTTP:header} и т. д.)

Разница в работе mod_rewrite в контексте.htaccess и в контексте VirtualHost

В контексте mod_rewrite работает с точностью до наоборот.

Как я говорил в начале статьи, все описанное выше касается применения mod_rewrite в контексте.htaccess. Если же mod_rewrite используется в , он будет работать по-другому: В в RewriteRule попадает весь путь запроса, начиная от первого слеша, заканчивая началом параметров GET: «http://example.com/some/news/category/post.html?comments_page=3 » → "/news/category/post.html". Эта строка всегда начинается со /. Второй аргумент RewriteRule также необходимо начинать со /, иначе будет «Bad Request». RewriteBase не имеет смысла. Проход правил происходит только один раз. Флаг [L] действительно заканчивает обработку всех правил, описанных в , без каких-либо последующих итераций.

Составление регулярных выражений

Старайтесь составлять регулярные выражения так, чтобы они наиболее узко определяли именно те запросы, которые вы хотите модифицировать - чтобы правила RewriteRule случайно не сработали для другого запроса. Например:

# Начинайте все регулярные выражения с "^" (признак начала строки) # и заканчивайте "$" (признак конца строки): RewriteRule ^news.php$ index.php # Даже если в этом нет необходимости - для универсальности и лучшего понимания конфигурации: RewriteRule ^news/(.*)$ index.php # Если под маску должны попадать только цифры - укажите это явно. # Если какие-то цифры постоянны, укажите их явно. # Если в оставшейся части запроса не могут присутствовать слеши, ограничьте их присутствие. # Не забывайте экранировать "." (точки). # Следующее правило нацелено на запросы вида http://example.com/news/2009/07/28/b-effect.html RewriteRule ^news/20{2}/{2}/{2}/[^/]+\.html index.php

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

Изменение внешних редиректов

Несмотря на то, что mod_rewrite позволяет изменять с помощью RewriteRule даже внешние редиректы, вплоть до протокола, я крайне не рекомендую делать это. В статье пример с изменением внешних редиректов используется только чтобы отвязаться от таких понятий как «ссылки» и «файлы» и более явно показать, что RewriteRule работает с простой строкой.

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

Как остановить бесконечный цикл

Иногда логика перенаправлений на сайте такова, что без специальных действий mod_rewrite воспринимает их как бесконечный цикл перенаправлений. Возьмем следующий пример.

На сайте была страница /info.html. Специалист по SEO решил, что поисковые системы будут лучше индексировать эту страницу, если она будет называться /information.html и попросил сделать внешний редирект с info.html на information.html. Однако разработчик сайта по каким-то своим соображениям не может просто переименовать info.html в information.html и сделать редирект - ему нужно, чтобы данные обязательно отдавались непосредственно из файла info.html. Он пишет следующее правило: # сделать внешний редирект RewriteRule ^info.html information.html # но по запросу /information.html все равно отдать info.html RewriteRule ^information.html info.html

… и сталкивается с бесконечным циклом. Каждый запрос /information.html получает внешний редирект снова на /information.html.

Решить эту проблему можно как минимум двумя способами. На Хабре был уже описан один из них - нужно установить переменную окружения и на основании ее значения прекращать перенаправления. Код будет выглядеть следующим образом:

RewriteCond %{ENV:REDIRECT_FINISH} !^$ RewriteRule ^ - [L] RewriteRule ^info.html$ information.html RewriteRule ^information.html$ info.html

Обратите внимание, что к имени переменной mod_rewrite добавляет "REDIRECT_".

Второй способ - проверить в THE_REQUEST, что именно было запрошено пользователем:

# Внешний редирект происходит только если пользователь запросил info.html. # Если же info.html - это результат внутреннего перенаправления, правило срабатывать не будет. RewriteCond %{THE_REQUEST} "^(GET|POST|HEAD) /info.html HTTP/+$" RewriteRule ^info.html$ information.html RewriteRule ^information.html$ info.html

Анализ исходного запроса пользователя - борьба с раскрытием ссылок Apache

При обработке запроса Apache раскрывает закодированные (URL -encoded) символы из первоначального запроса. В некоторых случаях это может быть нежелательно - разработчик хочет проверять именно первоначальный, немодифицированный запрос пользователя. Сделать это можно, проверяя в RewriteCond переменную %{THE_REQUEST}:

RewriteCond %{THE_REQUEST} ^GET[\ ]+/tag/([^/]+)/[\ ]+HTTP.*$ RewriteRule ^(.*)$ index.php?tag=%1 [L]