0

From this answer https://stackoverflow.com/a/59688271/7577919 I was able to decrpypt multiple PDFs in place using this bash script:

temp=`ls`; 
for each in $temp;
do qpdf --decrypt --replace-input $each;  
done

However, I had initially attempted to do this in Zsh (as it's encouraged in MacOS 10.15 Catalina), but was unable. It gave an error of output: File name too long

What is the difference between the for loops in Bash and Zsh and how would I go about writing a proper Zsh script?

CT Hall
  • 667
  • 1
  • 6
  • 27
  • 1
    Will `for i in *; do qpdf --decrypt --replace-input "$i"; done` work? (Watch out for the quotes) – dibery Jan 22 '20 at 03:56
  • Yes, that seems to work. Why? and why does it not need the ```temp=`ls` ```? – CT Hall Jan 22 '20 at 06:55
  • 1
    @dibery : If we are picky, your solution is not exactly equivalent. In the case of a directory without files, your solution would give an error message and set`each` do a single asterisk, while `ls` would expand to a null string. – user1934428 Jan 22 '20 at 07:25
  • @CTHall I guess there are some spaces in the filenames. You can debug it with issuing `set -x` before your script to see which file is the cause of this error. The asterisk expands to all files in that directory (if there is any file). – dibery Jan 22 '20 at 08:32
  • @user1934428 `zsh` does not treat an unmatched pattern as a literal string by default; it raises an error. Both `bash` and `zsh` can be configured to treat an unmatched pattern as an empty sequence (*not* a literal empty string), so that the loop would simply not be entered. – chepner Jan 22 '20 at 15:50
  • @chepner : Correct. That's why I had used the `(N)` qualifier in my answer for zsh. I should have mentioned this in my comment. – user1934428 Jan 23 '20 at 10:02
  • @user1934428 Yeah, I missed that you had an answer already before I commented. – chepner Jan 23 '20 at 12:27

2 Answers2

2

There is no difference in the for-loop, but in the way variables are expanded. Consider this program:

x='a b'
for v in $x
do
  echo $v
done

In bash, $x would word-split into 2 arguments, and hence the loop would be executed twice, once for a and once for b. In zsh, $x would not undergo word-splitting and the loop would be executed once, for the valud a b. This difference is everywhere when you expand a parameter.

In your case, the loop is executed once, each holding the complete output of the ls statement.

Of course in your case, it would be simpler in zsh to write the loop as

for each in *(N)

but if you really need a variable, I would use an array:

temp=(*(N))

The N-flag after the wildcard takes care that you get an empty string instead of an error message, if there are no files.

If you also want to catch the dot-files (similar to what a ls -A would do), use (ND) instead.

user1934428
  • 19,864
  • 7
  • 42
  • 87
  • I had a folder with just pdfs in it, but what if I had a folder with mixed files and just needed the pdfs? In bash I could set ` temp=`ls *.pdf` `but how would I do it in Zsh? – CT Hall Jan 22 '20 at 08:31
  • 1
    I don't see why you want to have a `temp` variable for your concrete, simple case, but if you do, it's simple: `temp=( *.pdf(N) )`. In bash, you would do a `temp=( *.pdf )`. Don't forget that when using an array, you would loop over it with `for f in ${temp[@]}` in zsh and with `for f in "${temp[@]}"` in bash. – user1934428 Jan 22 '20 at 12:09
  • To focussed on the original post I suppose. Thanks for your help. – CT Hall Jan 22 '20 at 14:54
2

Since parsing the result of ls is not encouraged, it's probably not recommended to use ls to get the pdf filenames. Instead, you can use find:

find . -name '*.pdf' -exec qpdf --decrypt --replace-input "{}" \;

You can limit pdf in current directory by adding -maxdepth 1 to find.

dibery
  • 2,760
  • 4
  • 16
  • 25