12

This question already has an answer here:

I am writing a script which loops into recent files in a folder and executes a command...

#!/bin/bash
cd /home/Downloads
recent_files = ($(ls -t | head -20))
for file in "${recent_files[@]}"
do
    ./cmd $file
done

I get the following syntax error:

line 3: syntax error near unexpected token `('
line 3: `recent_files = ($(\ls -t | head -20))'

marked as duplicate by dhag, Anthon, mdpc, Stephen Kitt, Networker May 11 '15 at 11:02

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

migrated from serverfault.com May 4 '15 at 3:29

This question came from our site for system and network administrators.

  • 3
    I suspect you also want cd $HOME/Downloads – Michael Durrant May 3 '15 at 13:14
  • I would use recent_files=($(ls -t | head -20)) instead of recent_files = ($(ls -t | head -20)) no space arround equal. – Archemar May 4 '15 at 8:29
  • 2
    10 upvotes? That is a lot of grateful classmates... – jasonwryan May 5 '15 at 9:03
24

Bash variable assignment scheme is var=value i.e. extra spaces are not allowed.

Removing them will correctly assign ls -t | head -20 output as an array.

So your script should be :

#!/bin/bash
cd /home/Downloads
recent_files=($(ls -t | head -20))
for file in "${recent_files[@]}"
do
    ./cmd $file
done
9

You don't need to store the output in a variable at all:

cd /home/Downloads
command ls -t | head -20 | while read -r file; do
    ./cmd "$file"
done

or even

command ls -t | head -20 | xargs -L 1 ./cmd

A safer way to work this is with stat. It still doesn't protect you from filenames with newlines, but

stat -c "%Y %n" * | sort -rn | head -20 | cut -d" " -f2- | xargs -L 1 ./cmd

To be foolproof, find -- probably need GNU tools for the null-byte handling

find . "${find_options[@]}" -printf '%T@ %p\0' | 
sort -zrn |
xargs -0 -L 1 sh -c './cmd "$(echo "$0" | sed "1s/^[0-9.]\\+ //")"'

the find_options array can be used as a placeholder to add filtering directives, such as

find_options=( -type f )
find_options+=( -maxdepth 1 )
find_options+=( -name '*.txt' )

and so on

  • Since it is generally recommended that the output of ls should not be used in scripts, I tried to figure out how to do this with find. I got as far as find . -maxdepth 1 -type f -printf '%c\t%f\n' -name '*', but the date is still hard to sort. It's not in epoch format yet. The tab is for use later with cut after sorting. AFAIK, the answer above (and all the others based on using ls) will potentially return directory names as well as file names - which might cause unexpected results with a command which expects to see a regular file. – Joe May 9 '15 at 2:38
5

You have to remove the the spaces around the equal sign.

recent_files=($(ls -t | head -20))

Then it should work.

  • Removing the outer parentheses would cause recent_files to be a scalar rather than an array, which the ${recent_files[@]} syntax on the next line indicates is not what the OP wants. – jwodder May 3 '15 at 3:15
3

I think there's a simpler answer...

#!/bin/bash
cd /home/Downloads
for file in $(ls -t | head -20)
do
    ./cmd $file
done
1

The error message says it all. However, as a help to such problems in the future, you can adopt some practices:

  1. Comment out the line that is throwing up an error like so:
recent_files=($(ls -t | head -20))

and run

bash -n myScript.sh

This will check errors in the script.

  1. Comment out a block of lines like so:

    : << 'EOF'
    lines
    of
    code
    EOF
    

and run

bash -n myScript.sh

Using this methodology, you will be able to zoom in on the erroneous line in your script.

Coming back to your specific problem, many have already answered what is causing it - the spaces around the = sign.