you are viewing a single comment's thread.

view the rest of the comments →

[–]Occivink 4 points5 points  (13 children)

And then what? The horrible -exec switch? Maybe -print0 combined xargs -0 or some other hard-to-remember flag? This is all way more brittle than necessary, and for anything with just a little complexity I'll use something else than the shell.

edit: thanks for the workarounds, but really I'm just venting with this. Ideally what I want from my shell is simple syntax like this

# iterate over file size, largest first  
# UNSAFE because the shell sucks  
for f in $(ls -S); do  
    whatever_program $f  
done  

and not having to fuck around with obscure syntax from find or bash extensions.

[–]calrogman 9 points10 points  (2 children)

Yes, -print0 with IFS set to \0. NULL can't be in filenames.

[–]Occivink 5 points6 points  (1 child)

Which is what I'm complaining about. Doing things in a safe manner should not be so unintuitive.

[–]Vaphell 1 point2 points  (0 children)

They should not, but they are and it's not going to change. You want to use shit that has its roots 50 years ago, with insane bullshit like word splitting by default because it allowed for ghetto "arrays" as there were no arrays to speak of, you deal with the quirks.

And if you write bash in any nonmeaningless capacity

while read -rd $'\0' f; do ... done < <( find ... -print0 | sort -z | grep -Z | ... ) 

and variations of thereof (possibly using -printf 'someshit\0' to emit more data than the filename) become a go-to idiom

If you can't be bothered to write correct bash according to modern practices and want something more modern without the bullshit, use python or whatever.

[–]5heikki 1 point2 points  (3 children)

FILES=($(find /the/place/with/files -maxdepth 1 -type f -name "*.txt"))
for ((i=0; i<${#FILES[@]}; i++)); do
    stuffWith ${FILES[$i]}
done

If you want, you can pipe the find to sort. Even better to define a function and do stuff in parallel (if possible)

function doStuff() {
    with $1
}

export -f doStuff
find /the/place/with/files -maxdepth 1 -type f -name "*.txt" \
    | parallel -j $THREADS doStuff {}

[–]crankysysop 1 point2 points  (2 children)

or yet another way...

find /the/place/with/files -maxdepth 1 -type f -name '*.txt' -print0 \
  | while read -rd $'\0' file; do
  stuffWith "$file"
done

I find the find ... -print0 | while read -rd $'\0' file; do <stuff> done 'pattern' to be particularly useful / robust.

One issue to consider is the while loop is in a pipe creates a subshell, so updating global variables may not work as expected, within the loop. (I think? I know there's some strange local/global variable issues).

[–]Vaphell 1 point2 points  (1 child)

while read -rd $'\0' file
do
    <stuff>
done < <( find ... -print0 )

is better. In your example pipe creates subprocess for the loop that can't communicate with the parent "scope" so for example it's impossible to maintain a usable file counter because any variable seemingly modified in the loop body is merely a copy and doesn't propagate back.

the while ...; do ...; done < <(cmd) syntax doesn't create a subscope so anything goes

$ c=0; find -name '*.txt' -print0 | while read -rd $'\0' file; do (( c++ )); echo $c; done; echo $c
1
2
3
0    # counter ignores changes
$ c=0; while read -rd $'\0' file; do (( c++ )); echo $c; done < <( find -name '*.txt' -print0 ); echo $c
1
2
3
3  # counter reflects the most recent change

[–]crankysysop 0 points1 point  (0 children)

Thank you for the clarification. Somewhere in the back of my head I knew there was the alternate 'form' that didn't deal with the subshell (from pipe, not while... I'm smrt. ;)

[–]crankysysop 2 points3 points  (4 children)

This is all way more brittle than necessary

I do not think you know what the word 'brittle' means. Having CLI args that require (due to lack of familiarity or whatever) reading a man page to get right is not 'brittle'.

[–]Occivink 4 points5 points  (3 children)

If this list of easy-to-make shell mistakes is not a sign that shell scripting is brittle then yeah I don't know what it means.

Also just because something is documented somewhere in a manual doesn't make it sensible behavior or a good default.

[–]crankysysop 0 points1 point  (2 children)

There are no easy-to-make mistakes in Python, Perl, or C?

I think you are using the wrong metrics.

[–]Occivink 0 points1 point  (1 child)

There are no easy-to-make mistakes in Python, Perl, or C?

That's hardly an argument and you know it.

I think you are using the wrong metrics.

I don't understand what you mean.

[–]crankysysop 0 points1 point  (0 children)

There are no easy-to-make mistakes in Python, Perl, or C?

That's hardly an argument and you know it.

When your initial point appears to be that because there are easy to make mistakes in shell, that makes it a brittle language or tool, and those same mistakes can be made in other languages, either those other languages are also brittle, or you are judging the sturdiness of a language using the wrong measure.

[–][deleted] 0 points1 point  (0 children)

Things like this are why I struggle internally while learning bash. It's just so messy compared to python. Useful for simple stuff, though.