Bootstrap

Linux 命令 pwd:探索当前工作目录的奥秘

在 Linux 系统中,pwd 命令是最基础、最常用的命令之一。它用于显示当前工作目录的完整路径。下面将深入探讨 pwd 命令的使用方法、工作原理以及源码解析,带你领略 Linux 文件系统的奥秘。

在这里插入图片描述

1. pwd 命令的使用

pwd 命令的使用非常简单,只需在终端输入 pwd 并按下回车键即可。

示例:

$ pwd
/root

上述命令将输出当前工作目录的路径 /root

2. pwd 命令的工作原理

pwd 命令的工作原理可以概括为以下几个步骤:

  1. 获取当前进程的文件描述符表: 每个进程都有一个文件描述符表,用于管理打开的文件和目录。
  2. 查找当前工作目录的文件描述符: 文件描述符表中有一个特殊的文件描述符(通常是 0),指向当前工作目录。
  3. 读取目录项: 通过文件描述符读取目录项,获取当前工作目录的 inode 号。
  4. 递归查找父目录: 从当前目录的 inode 号开始,递归查找父目录的 inode 号,直到根目录。
  5. 拼接路径: 将所有目录名拼接起来,形成完整的绝对路径。

3. pwd 命令的源码解析

3.1 源码位置

pwd 命令的源码通常可以在 GNU Coreutils 软件包中找到。你可以通过以下步骤下载和查看源码:

  1. 访问GNU项目的官方网站或使用git克隆coreutils的仓库

  2. 下载后,源代码会包含在你选择的目录中pwd 命令的源代码通常位于 src/pwd.c 文件中。

3.2 核心代码解析

以下是 pwd.c 文件的核心代码解析:

#include <config.h>
#include <getopt.h>
#include <stdio.h>
#include <sys/types.h>

#include "system.h"
#include "quote.h"
#include "root-dev-ino.h"
#include "xgetcwd.h"

/* 定义选项 */
static struct option const longopts[] =
{
  {"logical", no_argument, nullptr, 'L'},
  {"physical", no_argument, nullptr, 'P'},
  {GETOPT_HELP_OPTION_DECL},
  {GETOPT_VERSION_OPTION_DECL},
  {nullptr, 0, nullptr, 0}
};

/* 主函数 */
int main(int argc, char **argv)
{
  char *wd;
  /* POSIX requires a default of -L, but most scripts expect -P.
     Currently shells default to -L, while stand-alone
     pwd implementations default to -P.  */
  bool logical = (getenv ("POSIXLY_CORRECT") != nullptr);

  initialize_main (&argc, &argv);
  set_program_name (argv[0]);
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  atexit (close_stdout);

  while (true)
    {
      int c = getopt_long (argc, argv, "LP", longopts, nullptr);
      if (c == -1)
        break;
      switch (c)
        {
        case 'L':
          logical = true;
          break;
        case 'P':
          logical = false;
          break;

        case_GETOPT_HELP_CHAR;

        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);

        default:
          usage (EXIT_FAILURE);
        }
    }

  if (optind < argc)
    error (0, 0, _("ignoring non-option arguments"));

  if (logical)
    {
      wd = logical_getcwd ();
      if (wd)
        {
          puts (wd);
          return EXIT_SUCCESS;
        }
    }

  wd = xgetcwd ();
  if (wd != nullptr)
    {
      puts (wd);
      free (wd);
    }
  else
    {
      struct file_name *file_name = file_name_init ();
      robust_getcwd (file_name);
      puts (file_name->start);
      file_name_free (file_name);
    }

  return EXIT_SUCCESS;
}
3.2.1 头文件包含
#include <config.h>
#include <getopt.h>
#include <stdio.h>
#include <sys/types.h>

#include "system.h"
#include "quote.h"
#include "root-dev-ino.h"
#include "xgetcwd.h"

代码解析:

  • config.h:包含编译时的配置信息。
  • getopt.h:包含 getopt_long 函数,用于解析命令行选项。
  • stdio.h:包含 printffprintf 函数。
  • sys/types.h:包含系统数据类型定义。
  • system.hquote.hroot-dev-ino.hxgetcwd.h:包含 GNU Coreutils 项目的辅助函数和宏定义。
3.2.2 宏定义
static struct option const longopts[] =
{
  {"logical", no_argument, nullptr, 'L'},
  {"physical", no_argument, nullptr, 'P'},
  {GETOPT_HELP_OPTION_DECL},
  {GETOPT_VERSION_OPTION_DECL},
  {nullptr, 0, nullptr, 0}
};

定义了 pwd 命令的选项,包括 -L(逻辑路径)、-P(物理路径)、--help--version

3.2.3 主函数
int main(int argc, char **argv)
{
  char *wd;
  bool logical = (getenv ("POSIXLY_CORRECT") != nullptr);

  initialize_main (&argc, &argv);
  set_program_name (argv[0]);
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  atexit (close_stdout);

  while (true)
    {
      int c = getopt_long (argc, argv, "LP", longopts, nullptr);
      if (c == -1)
        break;
      switch (c)
        {
        case 'L':
          logical = true;
          break;
        case 'P':
          logical = false;
          break;

        case_GETOPT_HELP_CHAR;

        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);

        default:
          usage (EXIT_FAILURE);
        }
    }

  if (optind < argc)
    error (0, 0, _("ignoring non-option arguments"));

  if (logical)
    {
      wd = logical_getcwd ();
      if (wd)
        {
          puts (wd);
          return EXIT_SUCCESS;
        }
    }

  wd = xgetcwd ();
  if (wd != nullptr)
    {
      puts (wd);
      free (wd);
    }
  else
    {
      struct file_name *file_name = file_name_init ();
      robust_getcwd (file_name);
      puts (file_name->start);
      file_name_free (file_name);
    }

  return EXIT_SUCCESS;
}

代码解析:

  1. 初始化: 初始化程序名称、本地化设置等。
  2. 解析命令行选项: 使用 getopt_long 函数解析命令行选项,根据选项设置 logical 变量。
  3. 处理非选项参数: 忽略非选项参数。
  4. 获取当前工作目录:
    • 如果使用逻辑路径(logical 为 true),则调用 logical_getcwd 函数从环境变量 $PWD 中获取路径。
    • 如果使用物理路径(logical 为 false),则调用 xgetcwd 函数获取路径。如果 xgetcwd 失败,则调用 robust_getcwd 函数获取路径。
  5. 输出当前工作目录: 使用 puts 函数输出路径。
  6. 释放内存: 释放 xgetcwdrobust_getcwd 分配的内存。
3.2.4 辅助函数解析
3.2.4.1 logical_getcwd 函数
static char *
logical_getcwd (void)
{
  struct stat st1;
  struct stat st2;
  char *wd = getenv ("PWD");
  char *p;

  /* Textual validation first.  */
  if (!wd || wd[0] != '/')
    return nullptr;
  p = wd;
  while ((p = strstr (p, "/.")))
    {
      if (!p[2] || p[2] == '/'
          || (p[2] == '.' && (!p[3] || p[3] == '/')))
        return nullptr;
      p++;
    }

  /* System call validation.  */
  if (stat (wd, &st1) == 0 && stat (".", &st2) == 0 && psame_inode (&st1, &st2))
    return wd;
  return nullptr;
}

代码解析:

  1. 文本验证: 检查 $PWD 环境变量是否存在,并且是否以 / 开头。同时,检查路径中是否包含无效的 /./.. 序列。
  2. 系统调用验证: 使用 stat 函数检查 $PWD 路径和当前目录是否指向同一个 inode。
3.2.4.2 xgetcwd 函数

xgetcwd 函数是对 getcwd 函数的封装,增加了错误处理和内存分配功能。

3.2.4.3 robust_getcwd 函数
static void
robust_getcwd (struct file_name *file_name)
{
  size_t height = 1;
  struct dev_ino dev_ino_buf;
  struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf);
  struct stat dot_sb;

  if (root_dev_ino == nullptr)
    error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
           quoteaf ("/"));

  if (stat (".", &dot_sb) < 0)
    error (EXIT_FAILURE, errno, _("failed to stat %s"), quoteaf ("."));

  while (true)
    {
      /* If we've reached the root, we're done.  */
      if (PSAME_INODE (&dot_sb, root_dev_ino))
        break;

      find_dir_entry (&dot_sb, file_name, height++);
    }

  /* See if a leading slash is needed; file_name_prepend adds one.  */
  if (file_name->start[0] == '\0')
    file_name_prepend (file_name, "", 0);
}

代码解析:

  1. 获取根目录的设备和 inode 信息: 使用 get_root_dev_ino 函数获取根目录的设备和 inode 信息。
  2. 获取当前目录的 stat 信息: 使用 stat 函数获取当前目录的 stat 信息。
  3. 递归查找父目录: 从当前目录开始,递归查找父目录的 inode 号,直到根目录。
  4. 拼接路径: 将所有目录名拼接起来,形成完整的绝对路径。

4. pwd 命令的实际应用场景

4.1 基本应用

4.1.1 显示当前工作目录

最基本的应用场景是显示当前工作目录的完整路径。

$ pwd
/root

4.1.2 解析符号链接

默认情况下,pwd 命令不会解析符号链接。如果需要显示符号链接指向的真实路径,可以使用 -P 选项。

$ pwd -P

4.2 脚本编写

在编写脚本时,pwd 命令可以帮助获取当前工作目录的路径,从而简化路径处理。

4.2.1 创建文件或目录

在脚本中创建文件或目录时,可以使用 pwd 命令获取当前工作目录的路径,避免手动输入路径。

#!/bin/bash

# 获取当前工作目录
current_dir=$(pwd)

# 创建文件
touch "$current_dir/newfile.txt"

# 创建目录
mkdir "$current_dir/newdir"

4.2.2 执行脚本或命令

在执行脚本或命令时,可以使用 pwd 命令指定当前工作目录的路径。

#!/bin/bash

# 获取当前工作目录
current_dir=$(pwd)

# 执行脚本
"$current_dir/script.sh"

# 切换到当前工作目录并执行命令
cd "$current_dir" && command

4.2.3 自动化任务

在自动化任务中,pwd 命令可以帮助获取当前工作目录的路径,从而实现更灵活的脚本编写。

4.2.3.1 定时任务

在编写定时任务(如 cron 任务)时,可以使用 pwd 命令获取当前工作目录的路径,确保脚本在正确的目录下执行。

# 编辑 crontab 文件
$ crontab -e

# 添加定时任务
0 0 * * * /path/to/script.sh $(pwd)
4.2.3.2 持续集成/持续部署 (CI/CD)

在 CI/CD 流程中,pwd 命令可以帮助获取当前工作目录的路径,从而实现更灵活的构建和部署脚本。

# .gitlab-ci.yml 示例
stages:
  - build
  - deploy

build:
  stage: build
  script:
    - echo "Building in $(pwd)"
    - ./build.sh

deploy:
  stage: deploy
  script:
    - echo "Deploying from $(pwd)"
    - ./deploy.sh

4.2.4 调试与日志记录

在调试脚本或记录日志时,pwd 命令可以帮助获取当前工作目录的路径,从而提供更详细的上下文信息。

4.2.3.1 调试脚本

在调试脚本时,可以使用 pwd 命令记录当前工作目录的路径,帮助定位问题。

#!/bin/bash

# 记录当前工作目录
echo "Current directory: $(pwd)" >> debug.log

# 执行脚本
./script.sh
4.2.3.2 日志记录

在记录日志时,可以使用 pwd 命令获取当前工作目录的路径,提供更详细的上下文信息。

#!/bin/bash

# 记录当前工作目录
echo "Current directory: $(pwd)" >> logfile.log

# 执行命令
command >> logfile.log 2>&1

4.2.5 多线程环境

在多线程环境中,每个线程都有自己的当前工作目录。pwd 命令只显示调用它的线程的当前工作目录。

5. 总结

pwd 命令虽然简单,但它揭示了 Linux 文件系统的一些重要概念,例如文件描述符、inode、符号链接等。通过深入理解 pwd 命令的工作原理和源码实现,可以更好地掌握 Linux 文件系统的运作机制。

;