You are here:
Wiki-SL
>
TWikiBar Web
>
TWikiBarConversa006
(05 Feb 2008,
CollonsTorre
)
(raw view)
E
dit
A
ttach
---+!! Conversación de Bar Parte VI %TOC% ---++ Comandos de _Loop_ o Lazo (Continuación) - Que tal amigo mio, como estas? Ya lo sabes todo acerca del comando =for=?. Te dejé un ejercicio de deberes, y si no estoy equivocado, era para contar la cantidad de palabras de un archivo... Lo hiciste? - Claro! Estoy entusiasmando con ese lenguaje del _shell_, lo hice de la forma que me pediste, o sea sin usar el comando =wc= porque si no era mucho más fácil. Mira lo que hice... - Epa! Un momento! Realmente estás entusiasmando con el lenguaje, pero yo estoy deseando tomar un "chopp". Chico!, tráeme dos por favor. Uno sin espuma, como siempre! - Como te iba diciendo, mira lo que hice. Es muy fácil... %TERMINAL_INI% $ cat contpal.sh%OUT_INI% #!/bin/bash # Script meramente pedagógico cuya # función es contar la cantidad de palabras # de un archivo. Se supone que las # palabras están separadas entre sí # por espacio, <TAB> o <ENTER>. if [ $# -ne 1 ] then echo uso: $0 /camino/del/archivo exit 2 fi Cont=0 for Palabra in $(cat $1) do Cont=$((Cont+1)) done echo El archivo $1 tiene $Cont palabras.%OUT_FIM% %TERMINAL_FIM% O sea, el programa comienza como siempre verificando si el pasaje de parámetros fue correcto, en seguida el comando =for= se encarga de coger cada una de las palabras (recuerda que el =$IFS= patrón (default) es espacio, =<TAB>= y =<ENTER>=, que es exactamente lo que deseamos para separar las palabras), incrementando la variable =$Cont=. Vamos a recordar como es el archivo =ArchDelDOS.txt=. %TERMINAL_INI% $ cat <nop>ArqDoDOS.txt%OUT_INI% Este archivo fue generado por el DOS/Rwin y fue bajado por un ftp mal hecho.%OUT_FIM% %TERMINAL_FIM% Ahora vamos a testear el programa pasando este archivo como parámetro: %TERMINAL_INI% $ contpal.sh <nop>ArchDelDOS.txt%OUT_INI% El archivo <nop>ArchDelDOS.txt tiene 15 palabras%OUT_FIM% %TERMINAL_FIM% - Muy bien! funcionó correctamente! ---+++ Un Poco más de =for= y Matemática Volviendo a nuestro tema, la última vez que estuvimos aquí, terminamos nuestra conversación mostrando el _loop_ del =for= a continuación: <verbatim> for ((; i<=9;)) do let i++ echo -n "$i " done </verbatim> Una vez que llegamos a este punto, creo que sera bastante interesante citar que el Shell trabaja con el concepto de "Expansión Aritmética" (_Arithmetic Expansion_) que es accionado por una construcción de la forma =$((expresión))= o =let expressión= En el último =for= citado, usé la expansión de las dos formas, pero no podríamos seguir adelante sin saber que la expresión puede ser una de las listadas a continuación: <center> %TABLE{ sort="off" tableborder="0" cellpadding="4" cellspacing="1" headerbg="#0000FF" headercolor="#FFFF00" databg="#BBBBBB,#DDDDDD" headerrows="2" footerrows="1" }% | *Expansión Aritmética* || | *Expresión* | *Resultado* | | =id++ id--= | pós-incremento y pós-decremento de variables | | =++id -–id= | pré-incremento y pré-decremento de variables | | =**= | exponenciación | | =* / %= | multiplicación, división, resto de la división | | =+ -= | adición, substraccuión | | =<= >= < >= | comparación | | === !== | igualdad, desigualdad | | =&&= | Y lógico | | =||= | O lógico | </center> - Y si piensas que la conversación sobre _loop_ (o lazo) se termina en el comando =for=, estas en un gran error amigo, vamos a partir de ahora a ver dos más. ---+++ El comando =while= Todos los programadores conocen este comando, ya que es común a todas los lenguajes y en ellos lo que normalmente ocurre, es que un bloque de comandos es ejecutado, *en cuanto que* (en cuanto que en inglés es _while_) una determinada *condición* sea verdadera. Pues bien, esto es lo que ocurre en otros lenguajes! En programación _Shell_, el bloque de comandos es ejecutado *en cuanto que* un *comando* sea verdadero. Y esta claro, si quisiera verificar una condición, usaria el comando =while= junto con el comando =test=, exactamente como aprendiste a hacer en el =if=, recuerdas? Entonces, la sintaxis del comando queda de la siguiente forma: <verbatim> while comando do cmd1 cmd2 ... cmdn done </verbatim> y de esta forma el bloque de comandos formado por las instrucciones =cmd1=, =cmd2=,... y =cmdn= será ejecutado en cuanto que la ejecución de la instrucción =comando= sea ejecutada con éxito. Suponga la siguiente escena: tengo una tremenda gatita esperándome y estoy preso en el trabajo sin poder salir porque mi jefe, que es un tremendo rompe cocos se encuentra todavía trabajando en su escritorio, que queda bien en medio de mi salida a la calle. Él empezó a tener las antenas (probablemente instaladas en su cabeza por la esposa) atentas, después de la quinta vez que me vio pasar por su puerta y ver que todavía estaba allí. Volví a mi mesa e hice un _script_ en el servidor de esta forma: %TERMINAL_INI% $ cat logaute.sh%OUT_INI% #!/bin/bash while who | grep jefe do sleep 30 done echo El rompe se fue, no te entretengas, date el piro y ves enfrente%OUT_FIM% %TERMINAL_FIM% En este _scriptiziño_, el comando =while= verifica el _pipeline_ compuesto por el =who= y por el =grep= y que será verdadero en cuanto el =grep= localice la palabra =jefe= en la salida del =who=. Así, el _script_ dormirá durante 30 segundos mientras el jefe esté logado (Argh!). Cuando él se desconecte del servidor, el flujo del _script_ saldra del _loop_ y dará el tan ansiado mensaje de libertad. Si lo ejecuto adivinas lo que pasa? %TERMINAL_INI% $ logaute.sh%OUT_INI% jefe pts/0 Jan 4 08:46 (10.2.4.144) jefe pts/0 Jan 4 08:47 (10.2.4.144) ... jefe pts/0 Jan 4 08:52 (10.2.4.144)%OUT_FIM% %TERMINAL_FIM% Pues que cada 30 segundos es enviada a mi pantalla la salida del =grep=, lo que no es deseable ya que lleno la pantalla del computador y ademas el esperado mensaje podría pasar desapercibido. Para evitar eso ya sabemos que la salida del _pipeline_ tiene que ser redireccionada hacia =/dev/null=. %TERMINAL_INI% $ cat logaute.sh%OUT_INI% #!/bin/bash while who | grep jefe > /dev/null do sleep 30 done echo El rompe se fue, no te entretengas, date el piro y ves enfrentee%OUT_FIM% %TERMINAL_FIM% Ahora quiero montar un _script_ que reciba el nombre (y eventuales parámetros) de un programa que será ejecutado en _background_ y que me informe de su término. Pero, para entender este ejemplo, primero tengo que mostrar una nueva variable del sistema. Mira estos comandos escritos directamente en el _prompt_: %TERMINAL_INI% $ sleep 10&%OUT_INI% [1] 16317%OUT_FIM% $ echo $!%OUT_INI% 16317 [1]+ Done sleep 10%OUT_FIM% $ echo $!%OUT_INI% 16317%OUT_FIM% %TERMINAL_FIM% O sea, lance un proceso en _background_ que se ejecutara cada 10 segundos, solo para mostrar que la variable =$!= guarda el PID (_Process IDentification_) del último proceso en _background_, sin embargo, fíjate que después de la línea del =done=, la variable continuó con el mismo valor. Bien, sabiendo eso es más fácil de controlar cualquier proceso en _background_. Observa como: %TERMINAL_INI% $ cat monbg.sh%OUT_INI% #!/bin/bash # Ejecuta y controla un # proceso en background $1 & # Coloca en backgroud while ps | grep -q $! do sleep 5 done echo Fin del Proceso $1%OUT_FIM% %TERMINAL_FIM% Este _script_ es bastante similar al anterior, pero tiene unos trucos más, mira: tiene que ser ejecutado en _background_ para no retener el _prompt_ pero el =$!= será el del programa pasado como parámetro ya que fue colocado en _background_ después del =monbg.sh= propiamente dicho. Observa también la opción =-q= (_quiet_) del =grep=, sirve para transformarlo en un comando silencioso, o sea, para que el =grep= trabaje de forma invisible. Este mismo resultado podría ser obtenido si la línea fuera =while ps | grep $! > /dev/null=, como en los ejemplos que vimos hasta ahora. %DICA_INI% No te olvides: el _Bash_ dispone de la variable =$!= que posee el =PID= (_Process IDentification_) del último proceso ejecutado en _background_. %DICA_FIM% Vamos a mejorar el =musinc=, que es nuestro programa para incluir registros en el archivo =musicas=, pero antes necesito enseñarte a capturar un dato de la pantalla, y ya voy avisando: solo voy a dar una pequeña parte del comando _read_ (que es quien hace la captura de la pantalla) que sea lo suficiente para resolver este problema. En otra vuelta de "chopp" te lo voy a enseñar todo del asunto, inclusive como formatear la pantalla, pero hoy estamos hablando sobre _loops_. La sintaxis del comando =read= que nos interesa por ahora es la siguiente: %TERMINAL_INI% $ read -p "prompt de lectura" var %TERMINAL_FIM% Donde =prompt de lectura= es el texto que quieres que aparezca escrito en la pantalla, y cuando el operador escriba el dato éste irá a parar anla variable =var=. Por ejemplo: %TERMINAL_INI% $ read -p "Título del Álbun: " Tit %TERMINAL_FIM% Bien, una vez entendido eso, vamos a la especificación de nuestro problema: haremos un programa que inicialmente leerá el nombre del álbum y en seguida hará un _loop_ de lectura, extrayendo la música y el artista. Este _loop_ termina cuando se encuentre una música vacía, o sea, al ser solicitada la escritura de la música, el operador de un simple =<ENTER>=. Para facilitar la vida del operador, vamos a ofrecer como _default_ el mismo nombre del artista de la música anterior (ya que es normal que el álbum sea todo del mismo artista) hasta que él desee alterarlo. Vamos a ver como quedó ahora: %TERMINAL_INI% $ cat musinc%OUT_INI% #!/bin/bash # Catastra CDs (versión 4) # clear read -p "Título del Álbun: " Tit [ "$Tit" ] || exit 1 # Fin de la ejecución si el título= vacio if grep "^$Tit\^" musicas > /dev/null then echo Este álbum ya está catastrado exit 1 fi Reg="$Tit^" Cont=1 oArt= while true do echo Datos de la pista $Cont: read -p "Música: " Mus [ "$Mus" ] || break # Sale si vacio read -p "Artista: $oArt // " Art [ "$Art" ] && oArt="$Art" # Si vacio Art anterior Reg="$Reg$oArt~$Mus:" # Montando el registro Cont=$((Cont + 1)) # La linea anterior también podria ser ((Cont++)) done echo "$Reg" >> musicas sort musicas -o musicas%OUT_FIM% %TERMINAL_FIM% Este ejemplo, comienza con la lectura del título del álbum, y si no es introducido, terminará la ejecución del programa. En seguida un =grep= busca en el inicio (=^=) de cada registro de músicas, el título introducido seguido del separador (=^=) (que está precedido de una contrabarra (=\=) para protegerlo de la interpretación del _Shell_). Para leer los nombres de los artistas y las músicas del álbum, fue montado un _loop_ de =while= simple, que lo único que tiene a destacar es el hecho de estar almacenando el artista de la música anterior en la variable =$oArt= que solamente tendrá su contenido alterado, cuando alguno de los datos sea introducido en la variable =$Art=, o sea, cuando no se teclee un simple =<ENTER>= para mantener el artista anterior. Lo viste hasta ahora sobre el =while= fue muy poco. Este comando es muy utilizado, principalmente para lectura de archivos, sin embargo nos faltan conocimientos para continuar. Después que aprendamos a leer, veremos esta instrucción más a fondo. %DICA_INI% Lectura del archivo significa leer uno a uno todos los registros, lo que es siempre una operación lenta. Estate atento de no usar el =while= cuando su uso puede ser evitado. El _Shell_ tiene recursos como el =sed= y la familia =grep= que buscan en los archivos de forma optimizada sin ser necesario el uso de comandos de _loop_ para hacerlo registro a registro (o hasta palabra a palabra). %DICA_FIM% ---+++ El comando =until= El comando =until= funciona exactamente igual al =while=, sin embargo al revés. Dije todo pero no dije nada, no es cierto? Es lo siguiente: ambos verifican comandos; ambos poseen la misma sintaxis y ambos actuan en _loop_, sin embargo, mientras el =while= ejecuta el bloque de instrucciones del _loop_ *mientras* un comando este bien ejecutado, el =until= ejecuta el bloque del _loop_ *hasta que* el comando este bien ejecutado. Parece una diferencia insignificante, pero en cambio es fundamental. La sintáxis del comando es practicamente la misma del =while=. Observa: <verbatim> until comando do cmd1 cmd2 ... cmdn done </verbatim> Y así el bloque de comandos formado por las instruciones =cmd1=, =cmd2=,... y =cmdn= es ejecutado hasta que la ejecución de la instrucción =comando= sea bien ejecutada. Como te dije, el =while= y el =until= funcionan de forma antagónica lo cual es muy fácil de demostrar: en una guerra siempre que se inventa una arma, el enemigo busca una solución para neutralizarla. Basado en este principio de la guerra es que mi jefe, creó en el mismo servidor que yo ejecutaba el =logaute.sh= un _script_ para controlar el horário de mi llegada. Un dia ocurrió un problema en la red, y él me pidió que echara una mirada en su ordenador y me dejó solo en su sala. Inmediatamente comencé a revisar sus archivos - porque guerra es guerra - y mira lo que descubrí: %TERMINAL_INI% $cat llegada.sh%OUT_INI% #!/bin/bash until who | grep julio do sleep 30 done echo $(date "+ El %d/%m a las %H:%Mh") >> haragán.log%OUT_FIM% %TERMINAL_FIM% Que cara dura! Él estaba montando un _log_ con los horarios en que yo llegaba, y además llamó el archivo que me controlaba de =haragán.log=! Que será lo que quiso decir con eso? En este _script_, el _pipeline_ =who | grep julio=, será bien ejecutado solamente cuando =julio= sea encontrado en el comando =who=, o sea, cuando yo me "logue" en el servidor. Hasta que eso pase, el comando =sleep=, que forma el bloque de instrucciones del =until=, pondrá el programa en espera por 30 segundos. Cuando este loop se cierre, será enviado un mensaje hacia el =haragán.log= (ARGHH!). Suponiendo que el dia 20/01 yo me loguease a las 11:23 horas, el mensaje sería el siguiente: =El 20/01 a las 11:23h= Cuando vamos a buscar introducir, lo ideal sería que pudiésemos introducir diversos CDs, y en la última versión que hicimos del =musinc=, eso no ocurre, a cada CD que introducimos el programa termina. Veamos como mejorarlo: %TERMINAL_INI% $ cat musinc%OUT_INI% #!/bin/bash # Catastra CDs (versión 5) # Para= until [ "$Para" ] do clear read -p "Título del Álbum: " Tit if [ ! "$Tit" ] # Si titulo vacio... then Para=1 # Activé flag de salida else if grep "^$Tit\^" musicas > /dev/null then echo Este álbum ya está introducido exit 1 fi Reg="$Tit^" Cont=1 oArt= while [ "$Tit" ] do echo Dados de la pista $Cont: read -p "Música: " Mus [ "$Mus" ] || break # Sale si vacío read -p "Artista: $oArt // " Art [ "$Art" ] && oArt="$Art" # Si vacío Art anterior Reg="$Reg$oArt~$Mus:" # Montando registro Cont=$((Cont + 1)) # La linha anterior también podría ser ((Cont++)) done echo "$Reg" >> musicas sort musicas -o musicas fi done%OUT_FIM% %TERMINAL_FIM% En esta versión, fué agregado un _loop_ mayor antes de la lectura del título, que solo terminará cuando la variable =$Para= deje de estar vacía. En el caso de que el título del álbum no se encuentre, la variable =$Para= recibirá valor (en este caso coloqué =1= pero podría haber colocado cualquier cosa. Lo importante es que no este vacía) para salir de este _loop_, y terminar el programa. En el resto, el _script_ es idéntico a la versión anterior. ---+++ Atajos en loop No siempre un ciclo de programa, comprendido entre un =do= y un =done=, sale por la puerta del frente. En algunas oportunidades, tenemos que colocar un comando que aborte de forma controlada este _loop_. Al contrario, algunas veces deseamos que el fluxo de ejecución del programa vuelva antes de llegar al =done=. Para esto, tenemos respectivamente, los comandos =break= (que ya vimos rápidamente en los ejemplos del comado =while=) y =continue=, que funcionan de la siguiente forma: Lo que no habia mencionado anteriormente es que en sus sintáxis genéricas, aparecen de la siguiente forma: =break [ctd loop]= y =continue [ctd loop]= Donde _ctd loop_ representa la cantidad de _loops_ internos sobre los que estos comandos van a actuar. Su valor _default_ es 1. <center> <img src="%PUBURL%/%WEB%/FreeSkinImagens/Fluxograma.jpg" alt="Fluxograma" /> </center> Dudo mucho que nunca hayas borrado un archivo y enseguida no te dieras un golpe en la cabeza, maldiciéndote porque no debías haberlo hecho. Claro, pues yo la décima vez que hice esa burrada, cree un _script_ para simular una cesta de basura, o sea, cuando mando borrar uno (o varios) archivo(s), el programa "finge" que lo borró, pero en realidad lo que hizo fue mandarlo(s) para el diretório _/tmp/LoginName_del_usuario_. Llamé este programa de =erreeme= y en el _/etc/profile_ coloqué la siguiente línea: =alias rm=erreeme= El programa era así: %TERMINAL_INI% $ cat erreeme%OUT_INI% #/bin/bash # # Salvando Copia del Archivo Antes de Borrarlo # if [ $# -eq 0 ] # Tiene que haber uno o mas archivos para borrar then echo "Erro -> Uso: erreeme arch [arch] ... [arch]" echo " El uso de metacaracteres es permitido. Ej. erreeme arch*" exit 1 fi <nop>MiDir="/tmp/$LOGNAME" # Variable del sist. Contiene el nombre del usuario. if [ ! -d $MiDir ] # Si no exister mi directorio bajo el /tmp... then mkdir $MiDir # Voy a crearlo fi if [ ! -w $MiDir ] # Si no puedo grabar en el directorio... then echo Imposible grabar archivos en $MiDir. Cambie el permiso... exit 2 fi Erro=0 # Variable para indicar el cod. de retorno del prg for Arch # For sin el "in" recibe los parámetros pasados do if [ ! -f $Arch ] # Si este archivo no existe... then echo $Arch no existe. Erro=3 continue # Vuelve para el comando for fi !DirOrig=`dirname $Arch` # Cmd. dirname informa nombre del dir de $Arch if [ ! -w $DirOrig ] # Verifica permiso de grabación en el directorio then echo Sin permiso de borrar en el directorio de $Arch Erro=4 continue # Vuelve para el comando for fi if [ "$DirOrig" = "$MiDir" ] # Si estoy "vaciando la basurera"... then echo $Arch quedará sin copia de seguridad rm -i $Arch # Pregunta antes de borrar [ -f $Arch ] || echo $Arch borrado # Será que el usuario lo borró? continue fi cd $DirOrig # Guardo al final del archivo su directorio pwd >> $Arch # original para usarlo en un script de undelete mv $Arch $MiDir # Grabo y borro echo $Arch borrado done exit $Erro # Paso eventual número de error para el código de retorno%OUT_FIM% %TERMINAL_FIM% Como puedes ver, la mayor parte del _script_ es formada por pequeñas críticas a los parámetros hallados, pero como el _script_ puede haber recibido diversos archivos para borrar, a cada archivo que no se encaja dentro del especificado, hay un =continue=, para que la secuencia vuelva para el _loop_ del =for= de forma que pueda recibir otros archivos. Cuando estás en _Windows_ (con perdón de la mala palabra) y tratas de borrar aquella cantidad de basura con nombres extraños como =HD04TG.TMP=, si te da un error en uno de ellos, los otros no serán borrados, no es así? Entonces, el =continue= fue usado para evitar que un barbaridad de estas ocurra, o sea, aunque dé un error en el borrado de un archivo, el programa continuará borrando los otros que le fueron pasados. - Me parece que a esta altura ya debes tener curiosidad por ver el programa que restaura el archivo borrado, no es así? Entonces, ahí va un desafio: hazlo en casa y me lo traes para discutirlo en nuestro próximo encuentro aqui en el bar. - Caramba, me parece que en ese voy a fracasar, no sé ni como comenzar... - Amigo mio, este programa es como todo lo que se hace en Shell, extremamente fácil, es para ser hecho en no más de 10 líneas. No te olvides que el archivo borrado está grabado en =/tmp/$LOGNAME= y que su última línea es el directorio en que estaba antes de ser "borrado". Tampoco te olvides de comprobar si fue pasado el nombre del archivo a ser borrado. - En fin, voy a tratar, pero no sé... - Ten fé hombre, te estoy diciendo que es fácil! Cualquier duda me pasas un e-mail para <a href="mailto:julio.neves@gmail.com?Subject=Dudas Conversa de Bar">julio.neves@gmail.com</a>. Ahora basta de conversación que ya estoy con la garganta seca de tanto hablar. Me acompañas en el próximo "chopp" o vas a salir corriendo para hacer el script que pasé? - Déjame pensar un poco... - Mozo, trae otro "chopp" mientras él piensa! Voy aprovechar tambiém para mandar mi aviso publicitario: puedes decirle a los amigos que quien quiera hacer un curso nota diez de programación en _Shell_ que mande un e-mail para <a href="mailto:julio.neves@uniriotec.br?Subject=Curso de Shell con Julio Neves">julio.neves@uniriotec.br</a> para informarse. Gracias y hasta la <a href="https://twiki.softwarelivre.org/bin/view/TWikiBar/TWikiBarConversa007">próxima</a> -- Main.HumbertoPina - 28 Nov 2006
E
dit
|
A
ttach
|
P
rint version
|
H
istory
: r5
<
r4
<
r3
<
r2
<
r1
|
B
acklinks
|
V
iew topic
|
M
ore topic actions
Topic revision: r5 - 05 Feb 2008 - 21:45:19 -
CollonsTorre
TWikiBar
Página Inicial
Últimas alterações
Índice
Procurar
Estatísticas de Uso
Aviso de Atualização
Configurações Gerais
Projeto Gráfico
Mapa do Site
Quem Somos
Registre-se
?
Regras de Formatação
Biblioteca Gráfica
?
Carinhas Gráficas
Webs Wiki-SL
Amadeu
Anapolivre
ArquivoLivre
Arte
BahiaSocial
BeaBa
BibliotecaLivre
Blogs
BrasilDigital
BrasilELivre
BSM
Ccsa
CESL
CoberturaWiki
Cooperativas
Curriculo
DarvinMarosin
DiaD
Dinamicoop
Economia
EconomiaSolidaria
EducacaoLivre
Ekaaty
Emacsbr
ENSL
Fatos
Festival3
Festival4
Flisol
Fmpb
Formatos
Foswikibr
FSM2005
GNOMEBR
GTTemario2004
GTWeb
Guialivre
HDC
Incubus
InkscapeBrasil
Jogos
KdeBR
KSP
LGM
LinuxStokDoc
Livros
Main
Mentores
MHHOB
MinuanoDigital
MoradiaECidadania
OlhosDagua
Olimpo
OLPC
OOPTQ
Papers
PCLivre
PentahoBrasil
Pessoas
Portal
Prefeituras
PSLAL
PSLBA
PSLBancarios
PSLBrasil
PSLGO
PSLMA
PSLMG
PSLMIP
PSLMT
PSLMulheres
PSLPI
PubFisl10
PubFisl7
PubFisl8
PubFisl9
QuilomboDoSopapo
RadioSL
RedeMesh
RedePopular
RobotWars
Sandbox
Saudelivre
Scribus
Sementes
Shakya
SLRJ
SoftwareLivreIrece
SoftwareLivreVS
SoLiSC
SuporteLivre
System
Telecentros
TeseSA
TextoLivre
TV
TWikiBar
TWikiPtbr
UNELivre
UNIMIX
VilaTorres
WebNordeste
WTRD2004
Este Menu
?skin=free
English
Español
Português brasileiro
Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Wiki-SL?
Send feedback