Circumvent Argument list too long in script (for loop)

Viewed 760

I've seen a few answers regarding this, but as a newbie, I don't really understand how to implement that in my script.

it should be pretty easy (for those who can stuff like this)

I'm using a simple

for f in "/drive1/"images*.{jpg,png}; do 

but this is simply overloading and giving me

Argument list too long

How is this easiest solved?

2 Answers

Argument list too long workaroud

Argument list length is something limited by your config.

getconf ARG_MAX
2097152

But after discuss around differences between specifics and system (os) limitations (see comments from that other guy), this question seem wrong:

Regarding discuss on comments, OP tried something like:

ls "/simple path"/image*.{jpg,png} | wc -l
bash: /bin/ls: Argument list too long

This happen because of OS limitation, not !!

But tested with OP code, this work finely

for file in ./"simple path"/image*.{jpg,png} ;do echo -n a;done | wc -c
70980

Like:

 printf "%c" ./"simple path"/image*.{jpg,png} | wc -c

Reduce line length by reducing fixed part:

First step: you could reduce argument length by:

cd "/drive1/"
ls images*.{jpg,png} | wc -l

But when number of file will grow, you'll be buggy again...

More general workaround:

find "/drive1/" -type f \( -name '*.jpg' -o -name '*.png' \) -exec myscript {} +

If you want this to NOT be recursive, you may add -maxdepth as 1st option:

find "/drive1/" -maxdepth 1 -type f \( -name '*.jpg' -o -name '*.png' \) \
    -exec myscript {} +

There, myscript will by run with filenames as arguments. The command line for myscript is built up until it reaches a system-defined limit.

myscript /drive1/file1.jpg '/drive1/File Name2.png' /drive1/...

From man find:

   -exec command {} +
         This  variant  of the -exec action runs the specified command on
         the selected files, but the command line is built  by  appending
         each  selected file name at the end; the total number of invoca‐
         tions of the command will  be  much  less  than  the  number  of
         matched  files.   The command line is built in much the same way
         that xargs builds its command lines.  Only one instance of  `{}'

Inscript sample

You could create your script like

#!/bin/bash

target=( "/drive1" "/Drive 2/Pictures" )

[ "$1" = "--run" ] && exec find "${target[@]}" -type f \( -name '*.jpg' -o \
                         -name '*.png' \) -exec $0 {} +

for file ;do
    echo Process "$file"
done

Then you have to run this with --run as argument.

  • work with any number of files! (Recursively! See maxdepth option)

  • permit many target

  • permit spaces and special characters in file and directrories names

  • you could run same script directly on files, without --run:

     ./myscript hello world 'hello world'
     Process hello
     Process world
     Process hello world
    

Using pure

Using arrays, you could do things like:

allfiles=( "/drive 1"/images*.{jpg,png} )
[ -f "$allfiles" ] || { echo No file found.; exit ;}

echo Number of files: ${#allfiles[@]}

for file in "${allfiles[@]}";do
    echo Process "$file"
done

There's also a while read loop:

find "/drive1/" -maxdepth 1 -mindepth 1 -type f \( -name '*.jpg' -o -name '*.png' \) |
while IFS= read -r file; do

or with zero terminated files:

find "/drive1/" -maxdepth 1 -mindepth 1 -type f \( -name '*.jpg' -o -name '*.png' \) -print0 |
while IFS= read -r -d '' file; do
     
Related