试试这个 Bash 脚本,用于大型文件系统

一个简单的脚本,用于列出文件、目录、可执行文件和链接。
88 位读者喜欢这篇文章。
bash logo on green background

Opensource.com

你是否曾想列出一个目录中的所有文件,但只列出文件,没有其他内容?那么只列出目录呢?如果你有这样的需求,那么以下这个在 GPLv3 下开源的脚本,可能正是你一直在寻找的。

当然,你可以使用 find 命令

find . -maxdepth 1 -type f -print

但这既繁琐又难以输入,输出也不友好,并且缺乏 ls 命令的一些优化。你也可以结合 lsgrep 来实现相同的结果

ls -F . | grep -v /

但同样,这也很笨拙。这个脚本提供了一个简单的替代方案。

用法

该脚本提供了四个主要功能,这些功能取决于你调用的名称:lsf 列出文件,lsd 列出目录,lsx 列出可执行文件,而 lsl 列出链接。

无需安装脚本的多个副本,因为符号链接即可工作。这节省了空间,并使更新脚本变得更容易。

该脚本的工作原理是使用 find 命令进行搜索,然后在找到的每个项目上运行 ls 命令。这样做的好处是,传递给脚本的任何参数都会传递给 ls 命令。因此,例如,以下命令列出所有文件,甚至包括以点开头的文件

lsf -a

要以长格式列出目录,请使用 lsd 命令

lsd -l

你可以提供多个参数,以及文件和目录路径。

这将提供当前目录的父目录和 /usr/bin 目录中所有文件的长分类列表

lsf -F -l .. /usr/bin

然而,该脚本目前不处理递归。此命令仅列出当前目录中的文件。

lsf -R

该脚本不会深入任何子目录。这可能会在将来得到修复。

内部原理

该脚本以自顶向下的方式编写,初始函数位于脚本的开头,而主要工作部分位于结尾附近。脚本中真正重要的函数只有两个。parse_args() 函数仔细检查命令行,将选项与路径名分开,并从 ls 命令行选项中分离出脚本特定的选项。

list_things_in_dir() 函数接受目录名称作为参数,并在其上运行 find 命令。找到的每个项目都将传递给 ls 命令以进行显示。

结论

这是一个简单的脚本,用于完成一个简单的功能。它是一个节省时间的工具,并且在处理大型文件系统时非常有用。

脚本

#!/bin/bash

# Script to list:
#      directories (if called "lsd")
#      files       (if called "lsf")
#      links       (if called "lsl")
#  or  executables (if called "lsx")
# but not any other type of filesystem object.
# FIXME: add lsp   (list pipes)
#
# Usage:
#   <command_name> [switches valid for ls command] [dirname...]
#
# Works with names that includes spaces and that start with a hyphen.
#
# Created by Nick Clifton.
# Version 1.4
# Copyright (c) 2006, 2007 Red Hat.
#
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 3, or (at your
# option) any later version.

# It is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# ToDo:
#  Handle recursion, eg:  lsl -R
#  Handle switches that take arguments, eg --block-size
#  Handle --almost-all, --ignore-backups, --format and --ignore

main ()
{
  init
  
  parse_args ${1+"$@"}

  list_objects

  exit 0
}

report ()
{
  echo $prog": " ${1+"$@"}
}

fail ()
{
  report " Internal error: " ${1+"$@"}
  exit 1
}

# Initialise global variables.
init ()
{
  # Default to listing things in the current directory.
  dirs[0]=".";
  
  # num_dirs is the number of directories to be listed minus one.
  # This is because we are indexing the dirs[] array from zero.
  num_dirs=0;
  
  # Default to ignoring things that start with a period.
  no_dots=1
  
  # Note - the global variables 'type' and 'opts' are initialised in
  # parse_args function.
}

# Parse our command line
parse_args ()
{
  local no_more_args

  no_more_args=0 ;

  prog=`basename $0` ;

  # Decide if we are listing files or directories.
  case $prog in
    lsf | lsf.sh)
      type=f
      opts="";
      ;;
    lsd | lsd.sh)
      type=d
      # The -d switch to "ls" is presumed when listing directories.
      opts="-d";
      ;;
    lsl | lsl.sh)
      type=l
      # Use -d to prevent the listed links from being followed.
      opts="-d";
      ;;
    lsx | lsx.sh)
      type=f
      find_extras="-perm /111"
      ;;    
    *)
      fail "Unrecognised program name: '$prog', expected either 'lsd', 'lsf', 'lsl' or 'lsx'"
      ;;
  esac

  # Locate any additional command line switches for ls and accumulate them.
  # Likewise accumulate non-switches to the directories list.
  while [ $# -gt 0 ]
  do
    case "$1" in
      # FIXME: Handle switches that take arguments, eg --block-size
      # FIXME: Properly handle --almost-all, --ignore-backups, --format
      # FIXME:   and --ignore
      # FIXME: Properly handle --recursive
      -a | -A | --all | --almost-all)
        no_dots=0;
	;;
      --version)
	report "version 1.2"
	exit 0
	;;
      --help)
        case $type in
	  d) report "a version of 'ls' that lists only directories" ;;
	  l) report "a version of 'ls' that lists only links" ;;
	  f) if [ "x$find_extras" = "x" ] ; then
	       report "a version of 'ls' that lists only files" ;
	     else
	      report "a version of 'ls' that lists only executables";
	     fi ;;
	esac
	exit 0
	;;
      --)
        # A switch to say that all further items on the command line are
	# arguments and not switches.
	no_more_args=1 ;
	;;
      -*)
        if [ "x$no_more_args" = "x1" ] ;
	then
          dirs[$num_dirs]="$1";
          let "num_dirs++"
	else
	  # Check for a switch that just uses a single dash, not a double
	  # dash.  This could actually be multiple switches combined into
	  # one word, eg "lsd -alF".  In this case, scan for the -a switch.
	  # XXX: FIXME: The use of =~ requires bash v3.0+.
	  if [[ "x${1:1:1}" != "x-" && "x$1" =~ "x-.*a.*" ]] ;
	  then
            no_dots=0;
	  fi
          opts="$opts $1";
        fi
	;;
      *)
        dirs[$num_dirs]="$1";
        let "num_dirs++"
        ;;
    esac
    shift
  done

  # Remember that we are counting from zero not one.
  if [ $num_dirs -gt 0 ] ;
  then
    let "num_dirs--"
  fi
}

list_things_in_dir ()
{
  local dir

  # Paranoia checks - the user should never encounter these.
  if test "x$1" = "x" ;
  then
    fail "list_things_in_dir called without an argument"
  fi

  if test "x$2" != "x" ;
  then
    fail "list_things_in_dir called with too many arguments"
  fi

  # Use quotes when accessing $dir in order to preserve
  # any spaces that might be in the directory name.
  dir="${dirs[$1]}";

  # Catch directory names that start with a dash - they
  # confuse pushd.
  if test "x${dir:0:1}" = "x-" ;
  then
    dir="./$dir"
  fi
  
  if [ -d "$dir" ]
  then
    if [ $num_dirs -gt 0 ]
    then
      echo "  $dir:"
    fi

    # Use pushd rather passing the directory name to find so that the
    # names that find passes on to xargs do not have any paths prepended.
    pushd "$dir" > /dev/null
    if [ $no_dots -ne 0 ] ; then
      find . -maxdepth 1 -type $type $find_extras -not -name ".*" -printf "%f\000" \
        | xargs --null --no-run-if-empty ls $opts -- ;
    else
      find . -maxdepth 1 -type $type $find_extras -printf "%f\000" \
        | xargs --null --no-run-if-empty ls $opts -- ;
    fi
    popd > /dev/null
  else
    report "directory '$dir' could not be found"
  fi
}

list_objects ()
{
  local i

  i=0;
  while [ $i -le $num_dirs ]
  do
    list_things_in_dir i
    let "i++"
  done
}

# Invoke main
main ${1+"$@"}
标签

7 条评论

一个不错的补充是将脚本存储在其长名称下,并使用特殊选项创建所需的符号链接,例如
./script-large-files --createlinks

find . -maxdepth 1 -type f -exec ls -la {} \; 怎么样?

谢谢,应该很有用。
如果可以使用 ls 样式的颜色就更好了。我需要研究一下使用了哪些代码。

如果跨平台(BSD,MacOS)兼容性不是问题,你可以考虑使用 GNU getopt(1) 来解析命令行参数。不幸的是,Bash 的内置 getopts 命令不理解长选项。

顺便说一句,作为 zsh 用户,我通常会写成 “ls *(.)”,“ls -d *(/)” 用于非递归列表。只要你的目录中没有数千个文件,这就可以很好地工作。对于我的情况,“**/*(.)” 也非常有效,即使在我的大型主目录中也是如此。

我直接从这篇文章复制了脚本,并将其另存为 ls_script,没有 .sh 扩展名,然后对该文件执行了 chmod +x,并将其移动到我的 $PATH /usr/local/sbin 中。当我调用脚本时,我收到以下 stdout:“ls_script: 内部错误:无法识别的程序名称:'ls_script',预期为 'lsd'、'lsf'、'lsl' 或 'lsx'” 我哪里做错了?

将脚本保存为 “lsd”、“lsf”、“lsl” 或 “lsx” 等名称,然后为你创建的任何名称创建其他名称的符号链接...
例如:
保存为:lsd
ln -s lsd lsl
ln -s lsd lsf
ln -s lsd lsx

回复 ,作者 Daniel L Calloway (未验证)

我发现如果我对一个既有名为 work* 的目录,又有名为 work* 的文件的目录执行 'lsd work*',则会发生错误。没有进行全面的测试,但肯定存在问题。此外,在使用此脚本时,ls 和 exa 中方便的颜色编码消失了,但我想通过将 'ls $opts' 替换为 'ls --color $opts' 可以很容易地添加颜色编码

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.