List of files in the shell script

When I try to execute the code below, I get all the files whose file name starts with E

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

But if I try the code below that gets the template from the argument, I only get the name of the first file

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

Can someone help me solve this problem.

When I gave quotes like this myscript.sh 'E *', it worked fine, is there any way to do this without providing quotes?

+7
source share
6 answers

This is a shell expansion problem.

Your shell will interpret wildcards before passing it to your process. For example:

script.sh

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

Running above using a template:

 > ./script.sh E* Eva Eve Evolve 

If you want to pass an argument without interpreting the interpreter, first of all, you have to quote it:

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

Best solution using find :

What you are actually trying to do is get a list of all the files and folders in a given directory, in the order in which the change time changed (oldest). The ls output is known to be painful for parsing. It is preferable to use the powerful and flexible find .

This is one liner:

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

Most likely, it is mysterious, but it makes sense after an explanation.

  • Find all files in this directory without find ./ -maxdepth 1
  • For each file, print the last modified time in the second -printf "% A@
  • and their file name, delimited by null characters %f\0"
  • Sort strings separated by zeros, last modified time (in numerical form) sort -z -n
  • For each zero shared line, assign a timestamp “date” and the rest of the line “line”: while read -d '' date line
  • Print the line echo "$line"

For example, in my directory:

 > 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 ./ 

If you don’t need the result ./ , just take it out of your final set.

updated:. With Sorpigal's suggestion, process filenames with newlines.

Next, note the shell extension

This is the same as brackets and ampersands. For example, the following curl commands will not work as expected:

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

Because ampersands and brackets will be interpreted by the shell before they are passed to the process as arguments.

For proper operation you will have to quote the arguments:

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

Do you enclose your wildcard when calling the script?

 bash yourscript.sh 'E*' 

Otherwise, your script will get things that start with E in your current directory, and $ 1 becomes exactly the first file, so the behavior

+2
source

This sound is reminiscent of a shell expansion problem. If you want to transfer a wild card to the shell, just specify it. eg.

 ./script "E*" 

Quote avoid shell expansion.

+2
source

You should not use ls in a shell script. The ls output is intended for human consumption only and should never be used as an input to another command.

First, the right solution to your problem.

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

This assumes that you have no particular order or name requirements. You can refer to this answer if you need to arrange as ls -t .

Your problem is common, and I call it "Who sees what?" problem. You say myscript.sh E* and expect the literal E* be passed to your script, but in fact, since this argument is incorrect, glob will be expanded to bash before the script is called, so your script sees all the files in the current directory whose names start with E

As you can see, a single argument quotation mark “fixes” this. This is because bash does not perform any special extension inside single quotes, so now your script sees E* literally. In fact, you escaped * , so bash will not extend it before passing it to your script. To do this, without using quotes, you can perform a backslash *

 myscript.sh E\* 

But an excellent solution is to let bash extend glob and modify your script to handle multiple file arguments. For example:

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

Here I assume that all arguments will be file names. I changed data to an array and added quotation marks to all extensions in order to properly preserve spaces. This solution is still wrong because it parses the output of ls , but now you are likely to get the behavior you expect from unspecified E*

 myscript.sh E* 

In general, you should always quote the expansion of variables in shell scripts, because it probably does what you expect (and there are no extension quotes, probably not!)

+1
source

Here is an example:

find catalog files sorted by the date they were changed from older to newer, but less than or equal to a given date and containing ".inc" in their names

 "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) 

and name it:

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

I agree with @Sorpigal https://unix.stackexchange.com/questions/22674/shell-script-for-moving-oldest-files/29205#29205 , this is very useful, except for "IFS =" in the while loop, which breaks everything on my system (Ubuntu).

0
source

Try it.

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

All Articles