LINUX.ORG.RU

Декларация функции в bash

 , ,


0

2

Столкнулся со странностью недавно. Везде в интернетах пишут, что функцию в bash можно объявить разными способами, в том числе при помощи ключевого слова function перед названием функции. Именно такой способ мне больше всего нравится, и я решил им воспользоваться. Однако, не сработало.

Долго рылся в гугле, где-то видел пометку, что якобы function работает не во всех средах и т.п. Но это звучит как-то странно. Неужели в Ubuntu какая-то особая среда и т.п. Это ведь обычный дистрибутив Linux, один из самых популярных. Чего там может быть «особого»?

Например,


filetype_is1 () {
			echo $(file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2)	
		}

— сработало замечательно.

А вот так,

function filetype_is1 {
....
}

— вызвало ошибку (ссылка на неправильно поставленные скобки, что-то такое …).

Вопрос: у всех так? Почему первый способ работает, а второй - нет? Может подскажете ресурс, где хорошо объясняется эта тема? Перелопатил разных сайтов (англоязычных, естественно) штук 7-8. Вроде всё понятно объясняется, но не работает. Может действительно в bash это ключевое слово уже не используется, или что?

Ответ на: комментарий от Desmond_Hume

$SHELL это интерактивный шелл. Если ты запускаешь скрипт из файлика то она ни на что не влияет. Что у тебя в шебанге написано? Вангую - /bin/sh, а это симлинк на dash. Или пиши честно интерпретатор в шебанге, или соблюдай позиксовый синтакис.

$ PS1='... ' dash
... echo $SHELL
/bin/bash
... echo $0
dash
... cat /proc/$$/cmdline
dash...
anonymous
()

У меня в скриптах везде:

function func_name {
    echo "do something"
}

Исполбзую как раз ubuntu/debian, все прекрасно работает. Так что нужно больше подробностей.

anonymous
()
Ответ на: комментарий от vel
$ bash --version
GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ bash -n conv_test.sh 
$ 
Desmond_Hume ★★★★★
() автор топика
Ответ на: комментарий от anonymous
#!/bin/bash

#set -x
for a in /home/user/Documents/Test\ Files/*;
	do
		filetype_is1 () {
			echo $(file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2)	
		}		

		if [ $(filetype_is1) = "directory" ]; then
			:
		else			
			if [ $(filetype_is1) = "Unicode" ]; then
				mv "$a" /home/user/Documents/Test\ Files/test/${a%}.converted
			else
				:
			fi
		fi
	done

for b in /home/user/Documents/Test\ Files/*;
	do
		filetype_is2 () {
			echo $(file "$b" |  cut -d ":" -f 2 | cut -d " " -f 2)	
		}	
		if [ $(filetype_is2) = "directory" ]; then
			:
		else
			iconv -f WINDOWS-1251 -t UTF-8 "$b" -o "${b%}.converted" && mv "${b%}.converted" /home/user/Documents/Test\ Files/test/
		
		fi
	done 

for c in /home/user/Documents/Test\ Files/*;
	do 
		filetype_is3 () {
			echo $(file "$c" |  cut -d ":" -f 2 | cut -d " " -f 2)	
		}
		if [ $(filetype_is3) = "directory" ]; then
			:
		elif [ $(filetype_is3) = "ISO-8859" ]; then
			rm "$c"
		else
			:
		fi	
	done
			
#set +x

Вот именно так - работает, как надо. Стоит только function … сделать - всё, сыплет ошибками:

$ ./conv_test.sh 
mv: target '10).txt.converted.converted': No such file or directory
mv: target '11).txt.converted.converted': No such file or directory
mv: target '3).txt.converted.converted': No such file or directory
mv: target '4).txt.converted.converted': No such file or directory
mv: target '5).txt.converted.converted': No such file or directory
mv: target '6).txt.converted.converted': No such file or directory
mv: target '7).txt.converted.converted': No such file or directory
mv: target '8).txt.converted.converted': No such file or directory
mv: target '9).txt.converted.converted': No such file or directory
mv: target '(Copy).txt.converted.converted': No such file or directory
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
iconv: illegal input sequence at position 388
$ 
Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от targitaj

Тоже попробовал простейшую функцию с echo написать - всё нормально. С ключевым словом работает. Но в моём скрипте корявом работает только тот вариант, почему-то. Хоть бы ругался на что, было бы понятно, куда рыть. Молчаливо отрабатывает и всё тут …

$ cat ./test_func.sh 
#!/bin/bash

function test_echo {
	echo "It works!"
}

test_echo

$ ./test_func.sh 
It works!
$ 
Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)

В общем, function работает, хоть и сыплет ошибками. Что-то, видимо, со скриптом … странно, что при той записи ошибок нет. Тему закрываю, буду рыть в правильности скрипта, намудрил где-то, set’ом пройдусь, посмотрю, где косяк может быть.

Desmond_Hume ★★★★★
() автор топика
Ответ на: комментарий от Desmond_Hume

А function тут причем вообще? Если ты очевидно с кавычками накосячил?

Тот случай когда написать правильно быстрее и проще чем разбираться в быдлокоде.

Делается это примерно так:

find /foo/bar -maxdepth 1 -type f | while read f; do
    case $(file "$f" | cut -d: -f2 | cut -d' ' -f2) in
        Unicode) echo "moving $f";;
        ISO-8859) echo "deleting $f";;
        *) echo "converting $f -> $f.converted";;
    esac
done

Пять звезд, коллеги аноны…

anonymous
()
Ответ на: комментарий от anonymous

Ты бы хоть проверил сначала свой код. [facepalm.jpg]

Если ты очевидно с кавычками накосячил?

В каком месте?

Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от Desmond_Hume

Не буду я ничего проверять, и так сколько времени на тебя потратил. Я показал примерно как это дожно выглядеть, а не твоя портянка на 50 строк. Пофейспалми мне тут еще… функшен у него видите ли сломался…

В каком месте?

У тебя в ошибках ошметки имен файлов, очевидно, порвавшихся вдоль пробелов.

anonymous
()
Ответ на: комментарий от anonymous

Нету там «ошмётков» файлов, всё заэкранировано.

Я показал примерно как это дожно выглядеть

Мечтатель. Мне бы тоже хотелось летать, но вынужден ходить.

Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от Desmond_Hume

mv «$a» /home/user/Documents/Test\ Files/test/${a%}.converted

Ну здесь у вас явно ошибка, т.к. ${a%} может оказаться с пробелами. Не помню/не знаю можно ли еë просто в кавычки поместить в отличии от простого $a, так что изучите этот момент.

unDEFER ★★★★★
()
Ответ на: комментарий от unDEFER
#!/bin/bash

for a in /home/user/Documents/Test\ Files/*;
	do
		function filetype_is1 {
							
				echo $(file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2)	
		}		

		if [ $(filetype_is1) = "directory" ]; then
			:
		else			
			if [ $(filetype_is1) = "Unicode" ]; then
				mv "$a" /home/user/Documents/Test\ Files/test/${a%}.converted
			else
				:
			fi
		fi
	done

for b in /home/user/Documents/Test\ Files/*;
	do
		function filetype_is2 {
			echo $(file "$b" |  cut -d ":" -f 2 | cut -d " " -f 2)	
		}

		if [ $(filetype_is2) = "directory" ]; then
			:
		else
			iconv -f WINDOWS-1251 -t UTF-8 "$b" -o "${b%}.converted" && mv "${b%}.converted" /home/user/Documents/Test\ Files/test/
		
		fi
	done 

for c in /home/user/Documents/Test\ Files/*;
	do 
		function filetype_is3 {
			echo $(file "$c" |  cut -d ":" -f 2 | cut -d " " -f 2)	
		}
		if [ $(filetype_is3) = "directory" ]; then
			:
		elif [ $(filetype_is3) = "ISO-8859" ]; then
			rm "$c"
		else
			:
		fi	
	done

Тогда почему в таком виде работает? Добился того, что function наконец нормально отрабатывает. Скрипт теперь работает так, как хотелось изначально.

Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от unDEFER

Попробовал с пробелами, всё отлично конвертируется, складывается, а отработанный «мусор» уничтожается. Вот такие имена пробовал.

Ещё обратил внимание, что ошибки сразу ушли, как только в условии стал писать функцию именно в виде [ $(function) = … ], а не [ function = … ]. Хотя, казалось бы, второй вариант не считается же ошибкой? Выше писали, что должно работать и без $(), но по факту - нет.

Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 2)
Ответ на: комментарий от unDEFER

Получил работающий код. Хотел сделать переменной путь к папке, но не получилось, вечно эти пробелы мешают. Но с указанием самого пути и экранирования пробела - получилось. Вот:

#!/bin/bash

for a in /home/user/Documents/Test\ Files/*
	do
		function filetype_is {
		       file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2
		}	
		if [ $(filetype_is) = "directory" ]; then
			:
		elif [ $(filetype_is) = "Unicode" ]; then			
			mv "$a" "/home/user/Documents/Test Files/test/"
		elif [ $(filetype_is) = "ISO-8859" ]; then
			iconv -f WINDOWS-1251 -t UTF-8 -o "${a%}.converted" "$a" && mv "${a%}.converted" /home/user/Documents/Test\ Files/test/ 
		else 
			:
		fi
	done

find /home/user/Documents/Test\ Files/ -maxdepth 1 -type f  -exec mv {} /home/user/Documents/Test\ Files/original\ files/ \;
Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от unDEFER

А теперь получилось и с переменными. Фух … без всяких анонимов справился, получается.

#!/bin/bash

initial_folder="/home/$USER/Documents/Test Files/"
test_folder="/home/$USER/Documents/Test Files/test/"
originalFiles_folder="/home/$USER/Documents/Test Files/original files/"

for a in "$initial_folder"*
	do
		function filetype_is {
							
			file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2
		}		

		if [ $(filetype_is) = "directory" ]; then
			:
		elif [ $(filetype_is) = "Unicode" ]; then			
			mv "$a" "$test_folder"
		elif [ $(filetype_is) = "ISO-8859" ]; then
			iconv -f WINDOWS-1251 -t UTF-8 -o "${a%}.converted" "$a" && mv "${a%}.converted" "$test_folder" 
		else 
			:
		fi
	done

find "$initial_folder" -maxdepth 1 -type f  -exec mv {} "$originalFiles_folder" \;
Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 2)
Ответ на: комментарий от Desmond_Hume

Добавил временную метку, чтобы не было конфликта имён:

#!/bin/bash

now=$(date -d "today" +"%Y%m%d%H%M")
initial_folder="/home/$USER/Documents/Test Files/"
test_folder="/home/$USER/Documents/Test Files/test"
originalFiles_folder="/home/$USER/Documents/Test Files/original files/"

for a in "$initial_folder"*
	do
		function filetype_is {
							
			file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2
		}		

		if [ $(filetype_is) = "directory" ]; then
			:
		elif [ $(filetype_is) = "Unicode" ]; then			
			mv "$a" "$a.$now" && mv "$a.$now" "$test_folder"
		elif [ $(filetype_is) = "ISO-8859" ]; then
			iconv -f WINDOWS-1251 -t UTF-8 -o "${a%}.$now.converted" "$a" && mv "${a%}.$now.converted" "$test_folder" 
		else 
			:
		fi
	done

find "$initial_folder" -maxdepth 1 -type f  -exec mv {} "$originalFiles_folder" \;
Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от Desmond_Hume

Ты как был *** так и остался :( Тебя вот это не смущает?


for a in "$initial_folder"*
	do
		function filetype_is {
							
			file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2
		}		

Тут функция в теле цикла определяется. Диагноз ставить или сам поймёшь?

anonymous
()
Ответ на: комментарий от Desmond_Hume

Гораздо лучше, но есть ещё как минимум три замечания:

  1. Нет никакого смысла в конструкциях mv для переименования && mv для перемещения и iconv && mv. Надо сразу помещать файл в нужную директорию

  2. В данном случае нет никакого смысла в пустых ветвях if. Их можно безболезненно удалить.

  3. Нет никакой нужды трижды вызывать file. Перепишите filetype_is не как функцию, а как переменную сохраняющую результат выполнения file. Будет то же самое, но быстрее и эффективнее.

unDEFER ★★★★★
()

Тут всё очень плохо, проще показать сразу нормальный вариант.

Пример, скорее всего, нерабочий, так как «программирую» на форуме

#!/bin/sh

# folder - это у Билла Гейтса на рабочем столе
# в unix - катологи (или директории)
src_dir="/home/$USER/Documents/Test Files"
dst_dir="/home/$USER/Documents/Test Files/test"

for path in "$src_dir"/*
do
    # ключ "-f" - файл существует и является обычным файлом
    # (не каталогом/директорией, не символьной ссылкой,
    # не именованным pipe, не файлом устройства и тп)
    if [ -f "$path" ]; then
        # не надо проверять, что это директория
        # 
        # получаем кодировку
        # man file
        encoding="$(file -b --mime-encoding "$path")"

        # switch или pattern matching
        case "$encoding" in
            iso-8859-1) # если $encoding == iso-8859-1
                # то выполнем этот блок до ";;"

                # man basename
                # "/путь/до/файла.txt" -> "файла.txt"
                file_name="$(basename "$path")"
                # собираем путь, куда запишем сконвертированный файл
                dst_path="$dst_dir/$file_name.converted"
                # конвертируем
                iconv -f WINDOWS-1251 -t UTF-8 "$path" -o "$dst_path"
                # всё, записали сразу куда надо
            ;;
            *)
                # встретили файл в неизвестной кодировке
                # ничего не делаем
                # этот блок "*) ... ;;" можно удалить, если ничего не надо делать
            ;;
        esac
    fi
done

anonymous
()
Ответ на: комментарий от anonymous

В функции указана переменная, которая объявлена циклом. Исходил из того, что «лошадь идёт впереди телеги», а не наоборот. Код работает отлично. Какие проблемы? У тебя личная неприязнь к функциям, или что?

Desmond_Hume ★★★★★
() автор топика
Ответ на: комментарий от unDEFER

Нет никакого смысла в конструкциях mv для переименования && mv для перемещения и iconv && mv. Надо сразу помещать файл в нужную директорию.

Смысла, возможно, и нет, но с переменными что-то не получалось, поэтому разбил задачу на подзадачи. Попробую сократить, если удастся.

В данном случае нет никакого смысла в пустых ветвях if. Их можно безболезненно удалить.

Имеется вввиду else c : - это сделал для непредвиденных случаев. Например, кодировка файла совсем другая или он не подходит по типу, к примеру, вдруг по ошибке попадёт видеофайл. В общем, если что-то пойдёт не по сценарию сделал вариант «ничего не делать».

Нет никакой нужды трижды вызывать file. Перепишите filetype_is не как функцию, а как переменную сохраняющую результат выполнения file. Будет то же самое, но быстрее и эффективнее.

Сделал так исключительно из-за того, что был нехороший опыт с переменными, где был большой конвейер последующих команд. Эмпирически пришёл к выводу, что в таких случаях лучше пользоваться функциями. Попробую с переменной. Ещё смущает то, что переменная это «то, что не меняется и статически держится в ОЗУ», а функция это то, что динамически меняется. Не лучше ли всё-таки оставить функцию, раз file будет меняться при прохождении цикла по файлам и изменении его результата?

Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от anonymous

Знаешь, я тебе честно скажу, что опыта программирования у меня крайне мало, но даже я вижу огрехи в твоей портянке с бородатым нафталиновым юмором в комментариях. Вот скажи мне, что твоя портянка сделает, если вдруг кодировка окажется не ISO-8859-1, a ISO-8859, или ISO-8859-2, ISO-8859-3?

Desmond_Hume ★★★★★
() автор топика
Ответ на: комментарий от Desmond_Hume

В коментарии указано, что ничего.

Встречный вопрос: как ты собираешься различать эти «ISO-8859-1, a ISO-8859, или ISO-8859-2, ISO-8859-3»?

file и не пытается их различать, потому что windows-1251 сильно отличается от iso-8859 (iso-8859-5 с кирилликой). Но file говорит, что твои win1251 тексты в кодировке iso8859.

anonymous
()
Ответ на: комментарий от anonymous

А вот я тебе отвечаю - именно такая связка работает WINDOWS-1251 - обрати внимание, что выбираю именно WINDOWS-1251, а не ISO-8859. Почему - не знаю, но работает для моей задачи без ошибок.

Desmond_Hume ★★★★★
() автор топика
Ответ на: комментарий от Desmond_Hume

Почему - не знаю, но работает для моей задачи без ошибок.

Вот именно, ты не знаешь. Я объяснил как это работает, а ты даже не понял.

В твоем случае (для твоей баш-портянки) надо проверять, что файл текстовый, но не ‘Unicode text’ и конвертировать в юникод. Потому что после очередного обновления утилиты file, он может вернут совсем другой тип для твоих файлов win1251-кодировке.

anonymous
()
Ответ на: комментарий от anonymous

Нет, в этой папке могут только случайно оказаться файлы, отличные от текстовых. Только текстовые файлы туда помещаться будут. Задумка именно такая.

Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от unDEFER
#!/bin/bash

now=$(date -d "today" +"%Y%m%d%H%M")
initial_folder="$HOME/Documents/Test Files/"
test_folder="$HOME/Documents/Test Files/test"
originalFiles_folder="$HOME/Documents/Test Files/original files/"

for a in "$initial_folder"*
	do
		filetype_is=$(file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2)
		if [ $filetype_is = "directory" ]; then
			:
		elif [ $filetype_is = "Unicode" ]; then			
			mv "$a" "$a.$now" && mv "$a.$now" "$test_folder"
# не работает!			mv "$a" "$test_folder${a%}.$now"
		elif [ $filetype_is = "ISO-8859" ]; then
			iconv -f WINDOWS-1251 -t UTF-8 -o "${a%}.$now.converted" "$a" && mv "${a%}.$now.converted" "$test_folder"
# не работает!			iconv -f WINDOWS-1251 -t UTF-8 -o "$test_folder/${a%}.$now.converted" "$a" 
		else 
			:
		fi
	done

find "$initial_folder" -maxdepth 1 -type f  -exec mv {} "$originalFiles_folder" \;

Всё-таки помещение сразу по адресу - геморройный вариант, это придумывание новых переменных, в комментариях указал, что пытался сделать, но выходили ошибки из-за дублирующихся путей в переменных. Мой вариант с «конвейерным» mv всё же не так плох оказался. По крайней мере, он работает без проблем. А вот заменить функцию переменной - очень кстати оказалась. Скрипт побыстрее работает. Не сильно ощутимо, но мне так показалось.

Desmond_Hume ★★★★★
() автор топика
Ответ на: комментарий от unDEFER

В первом «не работает!»? Или во втором? В первом там сдвоенный путь идёт - сначала test_folder указывается путь от home, а $a ещё раз добавляет - получается что-то типа /home/user/file/home/user… Да, в общем-то, mv это не cp, в данном случае это особой роли не играет. Работает и так быстро. Вот с cp вариант был бы грустным, наверное.

Можно наверное придумать ещё две-три переменных, секущих пути, чтобы не было наложения. Типа x=/home/user/, y=/Documents/Test\ Files/ … типа такого. Но стоит ли?

Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 2)
Ответ на: комментарий от unDEFER

Не, не получилось. У меня скрипт лежит в другой папке, он начал перебирать файлы из той папки, почему-то))), проверять на file тип. А почему, если был переход cd?) Вот, блин … вот такой скрипт был:

#!/bin/bash

now=$(date -d "today" +"%Y%m%d%H%M")
initial_folder="$HOME/Documents/Test Files/"
test_folder="$HOME/Documents/Test Files/test"
originalFiles_folder="$HOME/Documents/Test Files/original files/"

cd $initial_folder
for a in *
	do
		filetype_is=$(file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2)
		if [ $filetype_is = "directory" ]; then
			:
		elif [ $filetype_is = "Unicode" ]; then			
			mv "$a" "$test_folder${a%}.$now"
		elif [ $filetype_is = "ISO-8859" ]; then
			iconv -f WINDOWS-1251 -t UTF-8 -o "$test_folder/${a%}.$now.converted" "$a"
		else 
			:
		fi
	done

find "$initial_folder" -maxdepth 1 -type f  -exec mv {} "$originalFiles_folder" \;

А, всё, увидел, я кавычки не поставил! … сейчас ещё раз попробую

А вот теперь работает. Забыл кавычки и слэш:

#!/bin/bash

now=$(date -d "today" +"%Y%m%d%H%M")
initial_folder="$HOME/Documents/Test Files/"
test_folder="$HOME/Documents/Test Files/test"
originalFiles_folder="$HOME/Documents/Test Files/original files/"

cd "$initial_folder"

for a in *
	do
		filetype_is=$(file "$a" |  cut -d ":" -f 2 | cut -d " " -f 2)
		if [ $filetype_is = "directory" ]; then
			:
		elif [ $filetype_is = "Unicode" ]; then			
			mv "$a" "$test_folder/${a%}.$now"
		elif [ $filetype_is = "ISO-8859" ]; then
			iconv -f WINDOWS-1251 -t UTF-8 -o "$test_folder/${a%}.$now.converted" "$a"
		else 
			:
		fi
	done

find "$initial_folder" -maxdepth 1 -type f  -exec mv {} "$originalFiles_folder" \;

Desmond_Hume ★★★★★
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 4)