在shell脚本中列出文件

当我尝试下面的代码获取文件名以E开头的所有文件

#!/bin/bash data=$(ls -trh E*) for entry in ${data} do echo ${entry} done 

但是,如果我尝试下面的代码,从参数获得通配符,我只获得第一个文件名

 #!/bin/bash data=$(ls -trh $1) for entry in ${data} do echo ${entry} done 

任何人都可以帮我解决这个问题..

当我给这样的myscript.sh引号“E *”它工作得很好,有没有办法做到这一点,而不报价?

这是一个shell扩展问题。

你的shell会在通过你的进程之前解释通配符。 例如:

script.sh

 #!/bin/bash echo $1 $2 $3 

用通配符运行上面的代码:

 > ./script.sh E* Eva Eve Evolve 

如果你想传递一个没有shell解释的参数,你必须引用它:

 > ./script.sh 'E*' E* 

使用find更好的解决方案

你实际上想要做的是以反向修改时间顺序(最早的第一个)获取给定目录中的所有文件和文件夹的列表。 ls的输出是非常痛苦的解析。 最好使用强大而灵活的find命令。

这是一个单行的:

 > find ./ -maxdepth 1 -printf "%A@ %f\0" | sort -z -n | while read -d '' date line; do echo "$line"; done 

这可能是神秘的,但一旦解释就是有道理的。

  • 找到这个目录中的所有文件,而不find ./ -maxdepth 1
  • 对于每个文件,在第二个-printf "%A@
  • 和它们的文件名,以空字符分隔%f\0"
  • 按照上次修改时间(数字) sort -z -n空分隔字符串sort -z -n
  • 对于每个空分隔的字符串,将时间戳分配给“日期”,将其余的行分配给“行”: while read -d '' date line
  • 打印行echo "$line"

例如,在我的目录中:

 > ls -l total 4296 drwxr-xr-x 2 bfer cvs 4096 2012-03-05 15:49 colortry drwxr-xr-x 3 bfer cvs 4096 2012-03-27 15:05 githug drwxr-xr-x 3 bfer cvs 4096 2012-03-12 17:18 hooks-bare drwxr-xr-x 3 bfer cvs 4096 2012-03-28 12:38 java -rw-r--r-- 1 bfer cvs 4025413 2012-03-27 12:53 mozdebug drwxr-xr-x 2 bfer cvs 4096 2012-02-16 12:54 mustache_bug_demo -rwxr-xr-x 1 bfer cvs 113 2012-03-30 12:20 try.sh > find ./ -maxdepth 1 -printf "%A@ %f\0" | sort -z -n | while read -d '' date line; do echo "$line"; done mozdebug colortry hooks-bare mustache_bug_demo githug java try.sh ./ 

如果你不想要./结果,就把它从你的最终集合中拿出来。

更新: Sorpigal建议用换行符处理文件名。

关于外壳扩展的进一步说明

这与括号和&符号一样。 例如,下面的curl命令将无法按预期方式工作:

 > curl example.com/site?q=hello&name=bob > echo 23/(7/98) | bc 

由于和号和括号在被作为参数传递给进程之前将被shell解释。

为了正确地工作,你必须引用args:

 > curl "example.com/site?q=hello&name=bob" > echo "23/(7/98)" | bc 

调用脚本时是否附上了通配符?

 bash yourscript.sh 'E*' 

否则你的脚本会在你当前的目录中得到以E开头的东西,$ 1就成为第一个文件,这就是行为

这听起来像一个壳牌扩展问题。 如果你想将通配符传递给shell,只需引用它。 例如

 ./script "E*" 

引用会避免shell扩展。

你不应该在shell脚本中使用lsls的输出只供人类使用,不能用作另一个命令的输入。

首先,正确的解决你的问题。

 #!/usr/bin/env bash pat="$1" while IFS= read -r -d '' file ; do echo "$file" done < <(find . -maxdepth 1 -name "$pat" -print0) 

这假定您没有任何特殊的排序或名称格式要求。 如果你需要像ls -t这样的命令,你可以参考这个答案 。

你的问题很常见,我把它称为“谁看到了什么?” 问题。 你说myscript.sh E*并期望一个文字E*被传递给你的脚本,但事实上,因为这个参数是不加引号的, glob会在你的脚本被调用之前被bash扩展,所以你的脚本看到的是所有的文件名称以E开头的当前目录。

正如你注意到的,单引号“修正”这个参数。 这是因为bash在单引号内没有执行任何特殊的扩展,所以现在你的脚本从字面上看E* 。 实际上,你已经逃脱了*所以bash在把它传递给你的脚本之前不会扩展它。 要做到这一点,而不使用引号是可能的反斜杠转义*

 myscript.sh E\* 

但是一个更好的解决方案是允许bash扩展glob,并改变脚本来处理多个文件参数。 例如:

 #!/bin/bash data=("$(ls -trh "$@")") for entry in "${data[@]}" do echo "${entry}" done 

这里我假定所有的参数都是文件名。 我已经将data更改为数组,并将引号添加到所有扩展,以便正确保留空白。 这个解决方案仍然是错误的,因为它解析了ls输出,但是现在你可能会得到你所期待的没有引号的E*

 myscript.sh E* 

一般来说,你应该总是引用shell脚本中的变量的扩展,因为这可能是你期望的(而且没有引用的扩展可能不会!)

这里有一个简单的例子:

根据修改日期排序的目录中的文件,从旧到新,但小于或等于给定日期,其名称中包含'.inc'

 "find-files.sh" #!/bin/bash store=$1 date_given=$2 inc_files=() limit=$( date +"%s" -d "$date_given" ) while read -r -d $'\0' seconds file; do seconds=${seconds%.*} if [[ $seconds -le $limit ]]; then printf "seconds=$seconds, file=$file\n" #block word splitting inc_files=( "${inc_files[@]}" "$file" ) #do whatever you want here... fi done < <(find $store -maxdepth 1 -type f -name "*.inc*" -printf "%T@ %f\0" | sort -zn) 

并称之为:

 ./find-files.sh '/a/dir/to/search' '2013-01-12 18:12:09' 

我同意@Sorpigal https://unix.stackexchange.com/questions/22674/shell-script-for-moving-oldest-files/29205#29205这是非常有帮助的除了“IFS =”在while循环,打破了我的一切系统(Ubuntu)。

尝试这个。

 #!/bin/bash data=$(ls -trh "$@" | cat -E) while [ ${#data} != 0 ] do echo ${data%%\$*} data=${data#*\$} done