在shell脚本中列出文件

时间:2012-03-30 09:52:06

标签: linux bash 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 *'的引号时,它工作正常,有没有办法在没有引号的情况下这样做?

6 个答案:

答案 0 :(得分:7)

这是一个shell扩展问题。

在传递给您的进程之前,您的shell将解释通配符。例如:

<强> script.sh

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

使用通配符运行上述内容:

> ./script.sh E*
Eva   Eve   Evolve

如果你想在不首先解释它的情况下传递参数,你必须引用它:

> ./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
  • 对以null分隔的字符串进行排序
  • 对于每个以null分隔的字符串,将时间戳指定为“date”,将该行的其余部分指定为“line”: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建议使用换行符处理文件名。

关于shell扩展的进一步说明

这与括号和&符号相同。例如,以下curl命令将无法按预期工作:

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

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

为了使其正常工作,您必须引用args:

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

答案 1 :(得分:2)

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

bash yourscript.sh 'E*'

否则,您的脚本将在当前目录中获取以E开头的内容,而$ 1将成为第一个文件,因此行为

答案 2 :(得分:2)

这听起来像是Shell扩展问题。如果你想将通配符传递给shell,只需引用它即可。例如。

./script "E*"

引用会避免shell扩展。

答案 3 :(得分:1)

您不应该在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等订单,可以参考this answer

你的问题很常见,我称之为&#34;谁看到了什么?&#34;问题。您说myscript.sh E*并希望将文字E*传递给您的脚本,但实际上由于此参数未加引号,glob将在您的脚本之前由bash展开调用,所以脚本看到的是当前目录中名称以E开头的所有文件。

正如您所说,单引号参数&#34;修复&#34;这个。这是因为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脚本中变量的扩展,因为这可能符合你的预期(而且不带引号的扩展可能!)

答案 4 :(得分:0)

这是一个简单的例子:

  

查找按修改日期排序的目录文件,从较旧到较新但小于或等于给定日期,并在其名称中包含“.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)。

答案 5 :(得分:-3)

试试这个。

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