-1

Error: Argument list too long

sudo cp and find... -exec sudo cp yield

/usr/bin/sudo(or find): Argument list too long

I get this error in my Travis CI build output. My .travis.yml file uses a shell script (deploy.sh) to run a git diff against branch2's force-app folder, and the output (several thousand files) is cp into a new directory:

sudo cp --parents $(git diff --name-only branch2 force-app/) $DEPLOYDIRECTORY;

The error is two lines:

./deploy.sh: line 122: /usr/bin/git: Argument list too long
./deploy.sh: line 122: /usr/bin/sudo: Argument list too long

I have the same issue with the subsequent find command. I use a naming convention to find and copy a file that corresponds with each file from the git diff:

for FILE in $GIT_DIFF_FILES; do
    if [[ $FILE == *Test.cls ]]; then
        find $classPath -maxdepth1 -samefile "$FILE-meta.xml" -exec sudo cp --parents {} $DEPLOY_DIRECTORY +
    fi;
done;

Again, I get the error: ./deploy.sh: line 142: /usr/bin/find: Argument list too long.

Note: Travis CI is running an Ubuntu Linux (Xenial) virtual environment for this build.


What I've Tried

1. Resetting the stack size

Travis CI automatically sets the stack size to 8192 and the arg max to 8388608 for each new build. In my deploy.sh file, I ulimit -s 9999999 to change the stack size to 9999999 and the arg max to 2559999744 for each new build.

2. Command Variations

For the first sudo cp command, I've tried:

  1. formatting command as a for loop
  2. tar -cf - -C files... | tar xpf - -C target directory...
  3. formatting command as git diff ... | xargs cp ...

For the find command, I've tried:

  1. find ... | xargs -n 1000 sudo cp ....
    • Any different flags added to this command don't work either.
  2. find ... -exec cp ...
    • Any flags or syntax changes return the same error

Changing the command doesn't appear to be the solution

After reproducing the error locally, I found that my commands already work fine when interacting with a smaller number of files (~1000 or less).

However, running the cp and find commands when the output of git diff is approximately 1200-1500 files or more then returns:

Argument list too long

In addition, simply running find or cp from my shell script also returns:

Argument list too long

It does not seem to matter what I do to these commands, therefore. Something more fundamental is causing the problem, but only when the file count of the git diff output exceeds approximately 1200 files.

How do I fix my cp and find commands?


How to Reproduce the Error

Here are the steps to take to reproduce this error according to my use case. I used VS Code, so I recommend you do as well to replicate this as similarly to me as possible. Note that you will need to replace USERNAME in the below code with your username.

Step 1: Fork this repo.

Step 2: Run each line of code below in your terminal (one at a time)

cd force-app/main/default

Create a new folder "diff". Then continue below.

git checkout -b branch2
cd force-app/main/default/classes

Add //comment to the bottom of myclass.cls and myclass.cls-meta.xml files. Save changes.

for n in {001..1500}; do cp myclass.cls myclass$n.cls; done
for n in {001..1500}; do cp myclass.cls-meta.xml myclass$n.cls-meta.xml; done
git add .
git commit -m “first commit”
git push --set-upstream origin branch2
git checkout master
for n in {001..1500}; do cp myclass.cls myclass$n.cls; done
for n in {001..1500}; do cp myclass.cls-meta.xml myclass$n.cls-meta.xml; done
git add .
git commit -m “second commit”
git push

cd back to the sfdx-travisci folder.

sudo cp -p $(git diff --name-only branch2 /Users/USERNAME/sfdx-travisci/force-app/main/default/classes) /Users/USERNAME/sfdx-travisci/force-app/main/default/diff

for file in $(sudo cp -p $(git diff --name-only branch2 /Users/USERNAME/sfdx-travisci/force-app/main/default/classes) /Users/USERNAME/sfdx-travisci/force-app/main/default/diff); do if [[ $file == *.cls ]]; then find /Users/USERNAME/sfdx-travisci/force-app/main/default/classes -samefile “$file-meta.xml” -exec sudo cp -p {} /Users/USERNAME/sfdx-travisci/force-app/main/default/diff +; fi; done;
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackexchange.com/rooms/111491/discussion-on-question-by-jack-barsotti-usr-bin-cpfind-argument-list-too-lo). – terdon Aug 06 '20 at 10:42
  • that didn't even seem to go offtopic IMO – ilkkachu Aug 06 '20 at 10:47

2 Answers2

-1

A complex copy command replacement:

tar -cf - -C /home/auser/data . | tar xpf - -C /home/auser/data2
prog9910
  • 101
  • 4
  • Experiment with first. Need to call in directory above ./data; I think. Comes from the FreeBSD manual. – prog9910 Aug 04 '20 at 20:13
  • From the `star` manual: `star -copy -C fromdir . todir`. This is the fastest copy method. Be careful to add the option `-no-fsync` if you are on a COW filesystem or on a generally slow filesystem. – schily Aug 04 '20 at 20:42
  • Here's what I tried instead of my `sudo cp $(git diff)` command: `tar -cf - -C $CHANGED_FILES | tar xpf - -C $DEPLOY_DIRECTORY`. The output was: **./deploy.sh: line 125: /bin/tar: Argument list too long ./deploy.sh: line 125: /bin/tar: Argument list too long**. Any suggestions? – Jack Barsotti Aug 04 '20 at 21:46
  • OK Try #2 for i in *; do cp "$i" ../prjshp/; done See Post: https://askubuntu.com/questions/217764/argument-list-too-long-when-copying-files – prog9910 Aug 04 '20 at 22:27
  • @prog9910 I've seen that post yeah. Hasn't worked for me either – Jack Barsotti Aug 04 '20 at 23:09
  • 1
    Rather than giving the list of files as a parameter, save it as a file and use `tar —files-from=FILE ....`. Also, wouldn’t it be easier to just use `git archive` to build tarballs? – jsbillings Aug 05 '20 at 02:14
  • Hrmm. Sounds like you're just trying to make it too complicated? Got to step back, restart with a new script, break into smaller pieces (*) If a space in your filenames, will cause issues, see this post: https://stackoverflow.com/questions/5241625/find-and-copy-files (*) Otherwise, find command performing cp examples. Or create a script to call from the script, to sub-divide the tasks. (*) Trying just performing ls to a file, see if something more basic just works. (*) Otherwise copy everything and cleanup after to remove unneeded files? I don't know. Good Luck – prog9910 Aug 05 '20 at 16:47
-1

Instead of trying some workarounds it is necessary to understand what the error is all about. The error is because the given argument exceeded the maximum argument length before invoking the command.

This may happen because

  • shell expansion produced a too long argument list
  • A process (like find) invoked a function of the exec family passing a too long argument list.

The command getconf ARG_MAX tells you the maximum length of arguments.

The command

cp $(git diff --name-only branch2 force-app/) ...

means that the command git is executed and then its output is passed as arguments to cp. If this output exceeds the maximum length of arguments, you get the said error.

The command

find ... -exec cp -t target {} +

is not much different because the trailing + to the find command tells find to pass the names of all found files in place of the curly braces.

So, the solution is to find ways to formulate your commands in ways that do not lead to huge argument strings. xargs or command substituion may be your friends.

As @AaronD.Marasco mentioned in their comment:

find ... -print0 | xargs -0 cp {} target

(xargs takes care of maximum argument length. Note that the curly braces here are an argument to xargs, not to find, but they have the same meaning).

Or with process substitution (less efficient in this case):

while IFS= read -d $'\0' -r Filename; do
  # do something with $Filename:
  cp "$Filename" target
done < <(find ... -print0)

For further details, please refer to the man pages of find and xargs.

rexkogitans
  • 1,319
  • 11
  • 17
  • 2
    the `find ... -exec cp -t target {} +` doesn't have any obviously large expansions. Note that the error shown is from the shell when starting `find`, not from `find` starting `cp`. And in any case, like `xargs`, `find -exec {} +` is supposed to be able to deal with argument size limits. – ilkkachu Aug 05 '20 at 09:30
  • the error shown in the question (well, the one I saw) is `./deploy.sh: line 142: /usr/bin/find: Argument list too long`. It doesn't start with `bash`, and it doesn't start with `/usr/bin/find`, but with the name of the script. That just so happens to be exactly the format Bash would print that error in. The error can't come from `find`, since `find` wouldn't know the name of the script, let alone the line number. Also with just `find -exec {} +`, there's no actual output, just `find` launching the command. And there's no shell involved in that. – ilkkachu Aug 05 '20 at 10:58
  • @ilkkachu Sure, thanks, I misread and deleted the misleading comment. – rexkogitans Aug 05 '20 at 11:48
  • Thanks for your answer. Running `find ... -print0 | xargs -0 cp {} target` still returns me the same error, however. I haven't had any luck with either xarg or exec variations. – Jack Barsotti Aug 05 '20 at 20:15
  • @JackBarsotti You have some `$(...)` in your commands. Check that these do not produce huge output (run the command within the parentheses alone). – rexkogitans Aug 06 '20 at 15:17
  • @rexkogitans Running `$(git diff ...)` does return a huge output (probably 1500-2000 file names). For my use case, I **must** copy this output to another directory and run a `find` so that I can parse the filenames. But I would assume that the `git diff` is what's causing my cp and find to blow up... – Jack Barsotti Aug 06 '20 at 15:53
  • @JackBarsotti Ok, so, use `while ... done < <(find ...)` to be on the safe side, even it is not quite fast - just as shown in my answer. – rexkogitans Aug 07 '20 at 19:18