соответствие выводу команды ls - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь кодировать команду ls. У меня есть следующая функция, которая печатает каждое имя файла:

int ft_list(const char *filename)
{
    DIR *dirp;
    struct dirent *dir;

    if (!(dirp = opendir(filename)))
        return (-1);
    while ((dir = readdir(dirp)))
    {
        if (dir->d_name[0] != '.')
            ft_putendl(dir->d_name);
    }
    closedir(dirp);
    return (0);
}

Команда ls печатает файлы, организованные в столбцы, чтобы соответствовать ширине экрана. Я читал об этом, и я думаю, что он использует стандартную библиотечную функцию ioctl, но я не могу найти какие-либо подробности. Как именно я могу это сделать?

1 Ответ

0 голосов
/ 31 октября 2018

Для того чтобы расположить файлы в столбцах, вам необходимо выяснить текущую ширину окна терминала. Во многих Unix-подобных системах (включая Linux и OS X) вы действительно можете использовать ioctl для получения этой информации, используя селектор TIOCGWINSZ.

Это именно то, что делает ls (в системах, которые поддерживают запрос ioctl), когда он определил, что стандартный вывод является терминалом (если только одностолбцовый формат не принудительно установлен с флагом -1). Если он не может определить ширину терминала, он использует 80.

Вот краткий пример того, как получить информацию. (В системах Linux вы можете найти подробности, набрав man tty_ioctl).

Для простоты следующий код предполагает, что stdout является файловым дескриптором 1. В ретроспективе, FILE_STDOUT было бы лучше. Если вы хотите проверить произвольно открытый файл, вам нужно будет использовать fileno, чтобы получить номер fd для FILE*.

/* This must come before any include, in order to see the
 * declarations of Posix functions which are not in standard C
 */
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>

/* If stdout is a terminal and it is possible to find out how many
 * columns its window has, return that number. Otherwise, return -1
 */
int window_get_columns(void) {
  struct winsize sizes;
  int cols = -1;
  if (isatty(1)) { 
    /* Only try this if stdout is a terminal */
    int status = ioctl(1, TIOCGWINSZ, &sizes);
    if (status == 0) {
      cols = sizes.ws_col;
    }
  }
  return cols;
}

/* Example usage */

/* Print a line consisting of 'len' copies of the character 'ch' */
void print_row(int len, int ch) {
  for (int i = 0; i < len; ++i) putchar(ch);
  putchar('\n');
}

int main(int argc, char* argv[]) {
  /* Print the first argument centred in the terminal window,
   * if standard output is a terminal
   */
  if (argc <= 1) return 1; /* No argument, nothing to do */
  int width = window_get_columns();
  /* If we can't figure out the width of the screen, just use the
   * width of the string
   */
  int arglen = strlen(argv[1]);
  if (width < 0) width = arglen;
  int indent = (width - arglen) / 2;
  print_row(width - 1, '-');
  printf("%*s\n", indent + arglen, argv[1]);
  print_row(width - 1, '-');
  return 0;
}

С момента написания вышеприведенного примера я отследил источник реализации Gnu ls; его (несколько более осторожный) вызов ioctl будет замечен здесь

...